diff --git a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt index 54326426e..bd7c491f6 100644 --- a/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt +++ b/packages/patrol/android/src/test/kotlin/pl/leancode/patrol/ContractsExtensionsTest.kt @@ -39,7 +39,7 @@ class DartTestGroupExtensionsTest { } // when - val dartTestFiles = dartTestGroup.listTestsFlat() + val dartTests = dartTestGroup.listTestsFlat() // then assertContentEquals( @@ -48,7 +48,7 @@ class DartTestGroupExtensionsTest { dartTestCase { name = "open_app_test open maps" }, dartTestCase { name = "webview_test interacts with the LeanCode website in a webview" }, ), - dartTestFiles, + dartTests, ) } @@ -96,7 +96,7 @@ class DartTestGroupExtensionsTest { } // when - val dartTestFiles = rootDartTestGroup.listTestsFlat() + val dartTests = rootDartTestGroup.listTestsFlat() // then assertContentEquals( @@ -112,7 +112,7 @@ class DartTestGroupExtensionsTest { dartTestCase { name = "open_app_test open maps" }, dartTestCase { name = "open_app_test open browser" }, ), - dartTestFiles, + dartTests, ) } } diff --git a/packages/patrol/ios/Classes/PatrolAppServiceClient.swift b/packages/patrol/ios/Classes/PatrolAppServiceClient.swift index 3cf153c21..ad6e210df 100644 --- a/packages/patrol/ios/Classes/PatrolAppServiceClient.swift +++ b/packages/patrol/ios/Classes/PatrolAppServiceClient.swift @@ -41,8 +41,6 @@ import NIO let request = Patrol_Empty() let response = try await client.listDartTests(request) - NSLog("RAW: Got tests: \(response.group)") - return response.group.listTestsFlat(parentGroupName: "").map { $0.name } diff --git a/packages/patrol/ios/Classes/PatrolIntegrationTestRunner.h b/packages/patrol/ios/Classes/PatrolIntegrationTestRunner.h index b5a123da6..ab8f5a83f 100644 --- a/packages/patrol/ios/Classes/PatrolIntegrationTestRunner.h +++ b/packages/patrol/ios/Classes/PatrolIntegrationTestRunner.h @@ -9,102 +9,102 @@ // For every Flutter dart test, dynamically generate an Objective-C method mirroring the test results // so it is reported as a native XCTest run result. -#define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ - @interface __test_class : XCTestCase \ - @end \ - \ - @implementation __test_class \ - \ - +(NSArray *)testInvocations { \ - /* Start native automation gRPC server */ \ - PatrolServer *server = [[PatrolServer alloc] init]; \ - [server startWithCompletionHandler:^(NSError * err) { \ - NSLog(@"Server loop done, error: %@", err); \ - }]; \ - \ - /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ - __block PatrolAppServiceClient *appServiceClient = [[PatrolAppServiceClient alloc] init]; \ - \ - /* Allow the Local Network permission required by Dart Observatory */ \ - XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; \ - XCUIElementQuery *systemAlerts = springboard.alerts; \ - if (systemAlerts.buttons[@"Allow"].exists) { \ - [systemAlerts.buttons[@"Allow"] tap]; \ - } \ - \ - /* Run the app for the first time to gather Dart tests */ \ - [[[XCUIApplication alloc] init] launch]; \ - \ - /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ - while (!server.appReady) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - \ - __block NSArray *dartTestFiles = NULL; \ - [appServiceClient \ - listDartTestsWithCompletionHandler:^(NSArray *_Nullable dartTests, NSError *_Nullable err) { \ - if (err != NULL) { \ - NSLog(@"listDartTests(): failed, err: %@", err); \ - } \ - \ - dartTestFiles = dartTests; \ - }]; \ - \ - /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ - while (!dartTestFiles) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - \ - NSLog(@"Got %lu Dart tests: %@", dartTestFiles.count, dartTestFiles); \ - \ - NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ - \ - /** \ - * Once Dart tests are available, we: \ - * \ - * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ - * \ - * Step 2. Create invocations to the generated methods and return them \ - */ \ - \ - for (NSString * dartTestFile in dartTestFiles) { \ - /* Step 1 - dynamically create test cases */ \ - \ - IMP implementation = imp_implementationWithBlock(^(id _self) { \ - [[[XCUIApplication alloc] init] launch]; \ - \ - __block RunDartTestResponse *response = NULL; \ - [appServiceClient runDartTestWithName:dartTestFile \ - completionHandler:^(RunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ - if (err != NULL) { \ - NSLog(@"runDartTestWithName(%@): failed, err: %@", dartTestFile, err); \ - } \ - \ - response = r; \ - }]; \ - \ - /* Wait until Dart test finishes */ \ - while (!response) { \ - [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ - } \ - \ - XCTAssertTrue(response.passed, @"%@", response.details); \ - }); \ - NSString *selectorName = [PatrolUtils createMethodNameFromPatrolGeneratedGroup:dartTestFile]; \ - SEL selector = NSSelectorFromString(selectorName); \ - class_addMethod(self, selector, implementation, "v@:"); \ - \ - /* Step 2 – create invocations to the dynamically created methods */ \ - NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ - NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ - invocation.selector = selector; \ - \ - NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", selectorName, signature); \ - \ - [invocations addObject:invocation]; \ - } \ - \ - return invocations; \ - } \ - \ +#define PATROL_INTEGRATION_TEST_IOS_RUNNER(__test_class) \ + @interface __test_class : XCTestCase \ + @end \ + \ + @implementation __test_class \ + \ + +(NSArray *)testInvocations { \ + /* Start native automation gRPC server */ \ + PatrolServer *server = [[PatrolServer alloc] init]; \ + [server startWithCompletionHandler:^(NSError * err) { \ + NSLog(@"Server loop done, error: %@", err); \ + }]; \ + \ + /* Create a client for PatrolAppService, which lets us list and run Dart tests */ \ + __block PatrolAppServiceClient *appServiceClient = [[PatrolAppServiceClient alloc] init]; \ + \ + /* Allow the Local Network permission required by Dart Observatory */ \ + XCUIApplication *springboard = [[XCUIApplication alloc] initWithBundleIdentifier:@"com.apple.springboard"]; \ + XCUIElementQuery *systemAlerts = springboard.alerts; \ + if (systemAlerts.buttons[@"Allow"].exists) { \ + [systemAlerts.buttons[@"Allow"] tap]; \ + } \ + \ + /* Run the app for the first time to gather Dart tests */ \ + [[[XCUIApplication alloc] init] launch]; \ + \ + /* Spin the runloop waiting until the app reports that it is ready to report Dart tests */ \ + while (!server.appReady) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + \ + __block NSArray *dartTests = NULL; \ + [appServiceClient \ + listDartTestsWithCompletionHandler:^(NSArray *_Nullable tests, NSError *_Nullable err) { \ + if (err != NULL) { \ + NSLog(@"listDartTests(): failed, err: %@", err); \ + } \ + \ + dartTests = tests; \ + }]; \ + \ + /* Spin the runloop waiting until the app reports the Dart tests it contains */ \ + while (!dartTests) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + \ + NSLog(@"Got %lu Dart tests: %@", dartTests.count, dartTests); \ + \ + NSMutableArray *invocations = [[NSMutableArray alloc] init]; \ + \ + /** \ + * Once Dart tests are available, we: \ + * \ + * Step 1. Dynamically add test case methods that request execution of an individual Dart test file. \ + * \ + * Step 2. Create invocations to the generated methods and return them \ + */ \ + \ + for (NSString * dartTest in dartTests) { \ + /* Step 1 - dynamically create test cases */ \ + \ + IMP implementation = imp_implementationWithBlock(^(id _self) { \ + [[[XCUIApplication alloc] init] launch]; \ + \ + __block RunDartTestResponse *response = NULL; \ + [appServiceClient runDartTestWithName:dartTest \ + completionHandler:^(RunDartTestResponse *_Nullable r, NSError *_Nullable err) { \ + if (err != NULL) { \ + NSLog(@"runDartTestWithName(%@): failed, err: %@", dartTest, err); \ + } \ + \ + response = r; \ + }]; \ + \ + /* Wait until Dart test finishes */ \ + while (!response) { \ + [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]]; \ + } \ + \ + XCTAssertTrue(response.passed, @"%@", response.details); \ + }); \ + NSString *selectorName = [PatrolUtils createMethodNameFromPatrolGeneratedGroup:dartTest]; \ + SEL selector = NSSelectorFromString(selectorName); \ + class_addMethod(self, selector, implementation, "v@:"); \ + \ + /* Step 2 – create invocations to the dynamically created methods */ \ + NSMethodSignature *signature = [self instanceMethodSignatureForSelector:selector]; \ + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; \ + invocation.selector = selector; \ + \ + NSLog(@"RunnerUITests.testInvocations(): selectorName = %@, signature: %@", selectorName, signature); \ + \ + [invocations addObject:invocation]; \ + } \ + \ + return invocations; \ + } \ + \ @end\ diff --git a/packages/patrol/lib/src/binding.dart b/packages/patrol/lib/src/binding.dart index 4237a676c..02a61d8b7 100644 --- a/packages/patrol/lib/src/binding.dart +++ b/packages/patrol/lib/src/binding.dart @@ -46,11 +46,10 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding { PatrolBinding() { final oldTestExceptionReporter = reportTestException; reportTestException = (details, testDescription) { - final currentDartTestFile = _currentDartTest; - if (currentDartTestFile != null) { + final currentDartTest = _currentDartTest; + if (currentDartTest != null) { assert(!constants.hotRestartEnabled); - _testResults[currentDartTestFile] = - Failure(testDescription, '$details'); + _testResults[currentDartTest] = Failure(testDescription, '$details'); } oldTestExceptionReporter(details, testDescription); }; @@ -175,7 +174,7 @@ class PatrolBinding extends IntegrationTestWidgetsFlutterBinding { void attachRootWidget(Widget rootWidget) { assert( (_currentDartTest != null) != (constants.hotRestartEnabled), - '_currentDartTestFile can be null if and only if Hot Restart is enabled', + '_currentDartTest can be null if and only if Hot Restart is enabled', ); const testLabelEnabled = bool.fromEnvironment('PATROL_TEST_LABEL_ENABLED'); diff --git a/packages/patrol/lib/src/native/patrol_app_service.dart b/packages/patrol/lib/src/native/patrol_app_service.dart index d2ce94444..37b684ee0 100644 --- a/packages/patrol/lib/src/native/patrol_app_service.dart +++ b/packages/patrol/lib/src/native/patrol_app_service.dart @@ -1,10 +1,12 @@ +//TODO: Use a logger instead of print + // ignore_for_file: avoid_print -// TODO: Use a logger instead of print import 'dart:async'; import 'dart:io' as io; import 'package:grpc/grpc.dart'; +import 'package:patrol/src/common.dart'; import 'package:patrol/src/native/contracts/contracts.pbgrpc.dart'; const _port = 8082; @@ -84,30 +86,32 @@ class PatrolAppService extends PatrolAppServiceBase { ); } - /// Returns when the native side requests execution of a Dart test file. + /// Returns when the native side requests execution of a Dart test. If the + /// native side requsted execution of [dartTest], returns true. Otherwise + /// returns false. /// - /// The native side requests execution by RPC-ing [runDartTest] and providing - /// name of a Dart test file. + /// It's used inside of [patrolTest] to halt execution of test body until + /// [runDartTest] is called. /// - /// Returns true if the native side requsted execution of [dartTestFile]. - /// Returns false otherwise. - Future waitForExecutionRequest(String dartTestFile) async { - print('PatrolAppService: registered "$dartTestFile"'); - - final requestedDartTestFile = await testExecutionRequested; - if (requestedDartTestFile != dartTestFile) { - // If the requested Dart test file is not the one we're waiting for now, - // it means that dartTestFile was already executed. Return false so that + /// The native side requests execution by RPC-ing [runDartTest] and providing + /// name of a Dart test that it wants to currently execute [dartTest]. + Future waitForExecutionRequest(String dartTest) async { + print('PatrolAppService: registered "$dartTest"'); + + final requestedDartTest = await testExecutionRequested; + if (requestedDartTest != dartTest) { + // If the requested Dart test is not the one we're waiting for now, + // it means that dartTest was already executed. Return false so that // callers can skip the already executed test. print( - 'PatrolAppService: registered test "$dartTestFile" was not matched by requested test "$requestedDartTestFile"', + 'PatrolAppService: registered test "$dartTest" was not matched by requested test "$requestedDartTest"', ); return false; } - print('PatrolAppService: requested execution of test "$dartTestFile"'); + print('PatrolAppService: requested execution of test "$dartTest"'); return true; }