I'm writing a C++ class ScriptProcess, meant to be used in QML, that acts as an interface to a child process. Said child process loads a script, then executes functions on demand. When you call a function, the result (be it a value or an exception) is returned asynchronously through signals.
import QtQuick 2.5
import QtTest 1.0
import dungeon 1.0
TestCase {
id: test
name: "ScriptProcessTest"
property ScriptProcess session: null
signal exceptionWhenLoadingCode(string type, int line, string message)
SignalSpy {
id: exceptionSpy
target: test
signalName: "exceptionWhenLoadingCode"
}
Component {
id: process
ScriptProcess {
id: script
onExceptionWhenLoadingCode: test.exceptionWhenLoadingCode(type, line, message)
}
}
function startScript(scriptUrl) {
var request = new XMLHttpRequest()
request.open("GET", "data/%1.js".arg(scriptUrl), false)
request.send(null)
return process.createObject(test, {code: request.responseText})
}
function cleanup() {
if (session) {
session.stop()
}
delete session
session = null
}
function test_syntaxErrorInGlobalContextIsReported() {
var count = exceptionSpy.count
session = startScript("syntax-error-in-global-context")
compare(exceptionSpy.count, count + 1)
}
function test_errorThrownInGlobalContextIsReported() {
var count = exceptionSpy.count
session = startScript("error-in-global-context")
compare(exceptionSpy.count, count + 1)
}
}
In a nutshell, I do the following:
For each test, open the auxillary process and load a script from a file. This is done by instantiating a Component, with the script given via the ScriptProcess.code property.
Run the test.
When the test finishes, kill the process and delete the object that manages it.
My problem is that the SignalSpy called exceptionSpy is not being triggered; exceptionSpy.count is always zero, and I have no idea why. Why is this the case? Am I misusing SignalSpy or Component?
XMLHttpRequest is asynchronous, so you should probably change startScript() to something like:
function startScript(scriptUrl) {
var object = process.createObject(test)
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readyState === XMLHttpRequest.DONE)
object.code = request.responseText
}
request.open("GET", "data/%1.js".arg(scriptUrl), false)
request.send(null)
return object
}
And since the signal is not going to be emitted right away, you'll have to wait for it instead of comparing the count immediately after creating the object:
function test_foo() {
var count = exceptionSpy.count
session = startScript("...")
tryCompare(exceptionSpy, "count", count + 1)
}
Related
okay, so I have a controller method which need to make a bunch of soap call to an external service, each one quite heavy. I am trying to do these one in parralel to save some time, but unless I build the async calls from GlobalScope, the deferred are resolved in sequence. Let me show you.
executing the following code
#ResponseBody
#GetMapping(path = ["/buildSoapCall"])
fun searchStations(): String = runBlocking {
var travels: List<Travel> = service.getTravels().take(500)
val deferred = travels
.map {
async() {
print("START")
val result = service.executeSoapCall(it)
print("END")
result
}
}
println("Finished deferred")
val callResults = deferred.awaitAll()
println("Finished Awaiting")
""
}
get me the following console message :
Finished deferred
START-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-END.....
the - is printed by executeSoapCall
As you can see, the deferred are called in sequence.
But if I use GlobalScope, like this :
#ResponseBody
#GetMapping(path = ["/buildSoapCall"])
fun searchStations(): String = runBlocking {
var travels: List<Travel> = service.getTravels().take(500)
val deferred = travels
.map {
GlobalScope.async() {
print("START")
val result = service.executeSoapCall(it)
print("END")
result
}
}
println("Finished deferred")
val callResults = deferred.awaitAll()
println("Finished Awaiting")
""
}
I get the following console message :
Finished Treating
STARTSTARTSTARTSTARTSTARTSTARTSTARTSTARTSTARTSTARTSTARTFinished deferred
START-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART--ENDENDSTARTSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-ENDSTART-END...START-END-END-END-END-END-END-END-END-END-END-END-ENDFinished Awaiting
showing that the Deferred are all starting in parallel. In addition, the treatment time is quite shorter.
I don't really understand why I have this behaviour.
Your call to service.executeSoapCall blocks the thread runBlocking coroutine is running on. You need to start async coroutine on a different thread everytime to get a concurrent behavior. You can achieve that by using a threadpool, e.g., Dispatchers.IO:
...
async(Dispatchers.IO) {
print("START")
val result = service.executeSoapCall(it)
print("END")
result
}
...
or creating a new thread on every call:
...
async(newSingleThreadContext("MyThread")) {
print("START")
val result = service.executeSoapCall(it)
print("END")
result
}
...
GlobalScope works because it uses a ThreadPool by default but you should avoid using it. You can read this article by Roman Elizarov about that topic.
I'm trying to optimise the performances in my app and I noticed that I do not remove Firestore listeners from my repository.
My repository has a number of functions that return a LiveData, that is then observed via Transformations from ViewModels and then the views.
One-time operations work absolutely fine (upload, delete etc.) but permanent listeners don't get garbage collected when the activity finishes.
Right now the function inside the repository looks like this:
// [...]
class Repository {
// [...]
fun retrieveCode() {
val observable = MutableLiveData<Code>()
val reference =
FirebaseFirestore.getInstance().collection(/**/).document(/**/)
reference
.addSnapshotListener { snapshot, exception ->
if(exception != null) {
observable.value = null
}
if(snapshot != null {
observable.value = snapshot.//[convert to object]
}
}
return observable
}
}
I found a workaround which is to create a custom LiveData object that handles the listener removal when it becomes inactive, like this:
class CodeLiveData(private val reference: DocumentReference):
LiveData<Code>(), EventListener<DocumentSnapshot>{
private var registration: ListenerRegistration? = null
override fun onEvent(snapshot: DocumentSnapshot?,
exception: FirebaseFirestoreException?) {
if(exception != null) {
this.value = null
}
if(snapshot != null) {
this.value = snapshot.//[convert to object]
}
}
override fun onActive() {
super.onActive()
registration = reference.addSnapshotListener(this)
}
override fun onInactive() {
super.onInactive()
registration?.remove()
}
}
Is there a way to solve this problem without creating a custom class, but rather by improving a function similar to the first example?
Thanks,
Emilio
There are two ways in which you can achieve this. The first one would be to stop listening for changes and this can be done in your onStop() function by calling remove() function on your ListenerRegistration object like this:
if (registration != null) {
registration.remove();
}
The approach would be to you pass your activity as the first argument in the addSnapshotListener() function, so Firestore can clean up the listeners automatically when the activity is stopped.
var registration = dataDocumentReference
.addSnapshotListener(yourActivity, listener)
I am writing test cases to test function with flexunit 4. I am using aysnc method.
But when I add two or more asyncHandlers to the instance. I meet the problem: Error: Asynchronous Event Received out of Order. How to resolve this problem? Thanks.
Code snippets:
[Test(order=1, async, description="synchronize content on line")]
public function testSynchronizeContentOnline():void
{
var passThroughData:Object = new Object();
var asyncHandler1:Function = Async.asyncHandler(this, authFailureHandler, 60000, null, timeoutHandler);
var asyncHandler:Function = Async.asyncHandler(this, authSuccessHandler, 60000, null, timeoutHandler);
caseManager.addEventListener(CaseAuthEvent.AUTH_SUCCESS,
asyncHandler);
caseManager.addEventListener(CaseAuthEvent.AUTH_FAILURE,
asyncHandler1);
caseManager.authenticate("admin", "admin");
trace('test');
}
private function timeoutHandler(event:Event):void
{
Assert.fail( "Timeout reached before event");
}
private var authFailed:Boolean = false;
private function authFailureHandler(event:CaseAuthEvent, passThroughData:Object):void
{
trace("authFailure:" + event.type);
authFailed = true;
}
private var authSucceed:Boolean = false;
private function authSuccessHandler(event:CaseAuthEvent, passThroughData:Object):void
{
trace("authSucceed:" + event.type);
authSucceed = true;
Assert.assertTrue(true);
}
That would be because you're adding order to your test cases, which is seems something else is dispatching before the first one is complete. To quote the ordering part of the flex unit wiki:
Your tests need to act independently of each other, so the point of
ordering your tests in a custom way is not to ensure that test A sets
up some state that test B needs. If this is the reason you are reading
this section, please reconsider. Tests need to be independent of each
other and generally independent of order.
Which I completely agree with. There should not be any order in your tests. The tests themselves sets the state of what needs to be done.
Your test will work if you test success and fail separately. So basically have 2 tests, one adds an async handler for your events success, the other for the events fail. Here is an example of the 2 tests as I would approach them...
[Test(async)]
public function testEventSuccess():void
{
var passThroughData:Object = new Object();
var asyncHandler:Function = Async.asyncHandler(this, authSuccessHandler, 60000, null, timeoutHandler);
caseManager.addEventListener(CaseAuthEvent.AUTH_SUCCESS,
asyncHandler);
caseManager.authenticate("admin", "admin");
}
[Test(async)]
public function testEventFailure():void
{
var passThroughData:Object = new Object();
var asyncHandler:Function = Async.asyncHandler(this, authFailureHandler, 60000, null, timeoutHandler);
caseManager.addEventListener(CaseAuthEvent.AUTH_FAILURE,
asyncHandler);
caseManager.authenticate("admin", "admin");
}
Remember to make a new instance of your caseManager in your set up function and its good practice to remove ref to it in the tearDown as the simple code snippet shows, I've just assumed the caseManager is of type CaseManager.
[Before]
public function setUp():void
{
caseManager = new CaseManager();
}
[After]
public function tearDown():void
{
caseManager = null;
}
I need to return the value from my Responder object. Right now, I have:
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
Basically, I need to know how to get the return value of ret_pr into id or anything that I return from that function. The responder just seems to eat it. I can't use a public variable somewhere else because this will be running multiple times at once, so I need local scope.
This is how I'd write a connection to the AMF server, call it and store the resulting value. Remember that the result won't be available instantly so you'll set up the responder to "respond" to the data once it returns from the server.
public function init():void
{
connection = new NetConnection();
connection.connect('http://10.0.2.2:5000/gateway');
setSessionID( 1 );
}
public function setSessionID(user_id:String):void
{
var amfResponder:Responder = new Responder(setSessionIDResult, onFault);
connection.call("ServerService.setSessionID", amfResponder , user_id);
}
private function setSessionIDResult(result:Object):void {
id = result.id;
// here you'd do something to notify that the data has been downloaded. I'll usually
// use a custom Event class that just notifies that the data is ready,but I'd store
// it here in the class with the AMF call to keep all my data in one place.
}
private function onFault(fault:Object):void {
trace("AMFPHP error: "+fault);
}
I hope that can point you in the right direction.
private function pro():int {
gateway.connect('http://10.0.2.2:5000/gateway');
var id:int = 0;
function ret_pr(result:*):int {
return result
}
var responder:Responder = new Responder(ret_pr);
gateway.call('sx.xj', responder);
return id
}
This code is never going to get you what you want. You need to use a proper result function. The anonymous function responder return value will not be used by the surrounding function. It will always return 0 in this case. You are dealing with an asynchronous call here, and your logic needs to handle that accordingly.
private function pro():void {
gateway.connect('http://10.0.2.2:5000/gateway');
var responder:Responder = new Responder(handleResponse);
gateway.call('sx.xj', responder);
}
private function handleResponse(result:*):void
{
var event:MyCustomNotificationEvent = new MyCustomNotificationEvent(
MyCustomNotificationEvent.RESULTS_RECEIVED, result);
dispatchEvent(event);
//a listener responds to this and does work on your result
//or maybe here you add the result to an array, or some other
//mechanism
}
The point there being using anon functions/closures isn't going to give you some sort of pseudo-syncronous behavior.
I am trying to use the following code to load styles from an external SWF, but I keep getting an ActionScript error when the URL is invalid:
Error: Unable to load style(Error #2036: Load Never Completed. URL: http://localhost/css/styles.swf): ../css/styles.swf.
at <anonymous>()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\styles\StyleManagerImpl.as:858]
private function loadStyles(): void
{
try
{
var styleEvent:IEventDispatcher =
StyleManager.loadStyleDeclarations("../styles.swf");
styleEvent.addEventListener(StyleEvent.COMPLETE,
loadStyle_completeHandler);
styleEvent.addEventListener(StyleEvent.ERROR,
loadStyle_errorHandler);
}
catch (error:Error)
{
useDefault();
}
}
private function loadStyle_completeHandler(event:StyleEvent): void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_completeHandler);
goToNextStep();
}
private function loadStyle_errorHandler(event:StyleEvent): void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_errorHandler);
useDefault();
}
I basically want to go ahead an use the default styles w/o the user seeing the error if this file can't be loaded - but I can't seem to find any way to do this.
Interesting problem. Try removing the removeEventListener call, or commenting it out; in my brief tests it appeared the event handler was being called twice (I'm not immediately sure why, although I suspect it has to do with style inheritance), and commenting that line did the trick.
If you have the same result, you might try just checking for the listener (using hasEventListener) first, before attaching it in your loadStyles() function, instead. Hope it helps!
** Not an answer, but an update:
FYI, this is the ActionScript code in the Source of mx.styles.StyleManagerImpl that is run when you call StyleManager.loadStyleDeclarations(). I ran the debugger and added a breakpoint at the Line 858("throw new Error(errorText);"), and the breakpoint was caught. I'm thinking it shouldn't be caught there, but the previous IF ("if (styleEventDispatcher.willTrigger(StyleEvent.ERROR))") should be run instead.
public function loadStyleDeclarations2(
url:String, update:Boolean = true,
applicationDomain:ApplicationDomain = null,
securityDomain:SecurityDomain = null):
IEventDispatcher
{
var module:IModuleInfo = ModuleManager.getModule(url);
var readyHandler:Function = function(moduleEvent:ModuleEvent):void
{
var styleModule:IStyleModule =
IStyleModule(moduleEvent.module.factory.create());
styleModules[moduleEvent.module.url].styleModule = styleModule;
if (update)
styleDeclarationsChanged();
};
module.addEventListener(ModuleEvent.READY, readyHandler,
false, 0, true);
var styleEventDispatcher:StyleEventDispatcher =
new StyleEventDispatcher(module);
var errorHandler:Function = function(moduleEvent:ModuleEvent):void
{
var errorText:String = resourceManager.getString(
"styles", "unableToLoad", [ moduleEvent.errorText, url ]);
if (styleEventDispatcher.willTrigger(StyleEvent.ERROR))
{
var styleEvent:StyleEvent = new StyleEvent(
StyleEvent.ERROR, moduleEvent.bubbles, moduleEvent.cancelable);
styleEvent.bytesLoaded = 0;
styleEvent.bytesTotal = 0;
styleEvent.errorText = errorText;
styleEventDispatcher.dispatchEvent(styleEvent);
}
else
{
throw new Error(errorText);
}
};
module.addEventListener(ModuleEvent.ERROR, errorHandler,
false, 0, true);
styleModules[url] =
new StyleModuleInfo(module, readyHandler, errorHandler);
// This Timer gives the loadStyleDeclarations() caller a chance
// to add event listeners to the return value, before the module
// is loaded.
var timer:Timer = new Timer(0);
var timerHandler:Function = function(event:TimerEvent):void
{
timer.removeEventListener(TimerEvent.TIMER, timerHandler);
timer.stop();
module.load(applicationDomain, securityDomain);
};
timer.addEventListener(TimerEvent.TIMER, timerHandler, false, 0, true);
timer.start();
return styleEventDispatcher;
}
I debugged the source, and found that the ERROR Event is being triggered twice. So, I simply set a flag the first time the ERROR event handler is triggered, and check that flag for a value of true before continuing:
private var isErrorTriggered:Boolean; // Default is false
private function loadStyle_completeHandler(event:StyleEvent):void
{
IEventDispatcher(event.currentTarget).removeEventListener(
event.type, loadStyle_completeHandler);
goToNextStep();
}
private function loadStyle_errorHandler(event:StyleEvent):void
{
if (isErrorTriggered)
return;
isErrorTriggered = true;
useDefault();
}