From 96957e7b35b91e683df7961f8a4371c630c121a6 Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Thu, 30 Sep 2021 16:19:21 +0900 Subject: [PATCH 01/11] Use TargetManager with "with" statement --- tools/commands/integration_test.py | 82 ++++++++++++++++-------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 899e9dfca..d64cec977 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -139,7 +139,14 @@ class TargetManager: def __init__(self): self.target_per_id = {} self.targets_per_platform = defaultdict(list) + + def __enter__(self): self._find_all_targets() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.target_per_id.clear() + self.targets_per_platform.clear() def exists_platform(self, platform): return len(self.targets_per_platform[platform]) > 0 @@ -286,15 +293,6 @@ def _integration_test(plugin_dir, platforms, timeout): """ plugin_name = os.path.basename(plugin_dir) - target_manager = TargetManager() - if not platforms: - platforms.extend(target_manager.platforms()) - if not platforms: - return [ - TestResult.fail(plugin_name, - errors=['Cannot find any connected targets.']) - ] - example_dir = os.path.join(plugin_dir, 'example') if not os.path.isdir(example_dir): return [ @@ -327,35 +325,43 @@ def _integration_test(plugin_dir, platforms, timeout): ] try: - errors = [] - completed_process = subprocess.run('flutter-tizen pub get', - shell=True, - cwd=example_dir, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE) - if completed_process.returncode != 0: - if not completed_process.stderr: - errors.append('pub get failed. Make sure the pubspec file \ - in your project is valid.') - else: - errors.append(completed_process.stderr) - return [TestResult.fail(plugin_name, errors=errors)] - - test_results = [] - - for platform in platforms: - if not target_manager.exists_platform(platform): - test_results.append( - TestResult.fail( - plugin_name, platform, - [f'Test runner cannot find any {platform} targets.'])) - continue - targets = target_manager.get_by_platform(platform) - for target in targets: - result = target.run_integration_test(plugin_name, example_dir, - timeout) - test_results.append(result) - + with TargetManager() as target_manager: + if not platforms: + platforms.extend(target_manager.platforms()) + if not platforms: + return [ + TestResult.fail( + plugin_name, + errors=['Cannot find any connected targets.']) + ] + errors = [] + completed_process = subprocess.run('flutter-tizen pub get', + shell=True, + cwd=example_dir, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + if completed_process.returncode != 0: + if not completed_process.stderr: + errors.append('pub get failed. Make sure the pubspec file \ + in your project is valid.') + else: + errors.append(completed_process.stderr) + return [TestResult.fail(plugin_name, errors=errors)] + + test_results = [] + + for platform in platforms: + if not target_manager.exists_platform(platform): + test_results.append( + TestResult.fail(plugin_name, platform, [ + f'Test runner cannot find any {platform} targets.' + ])) + continue + targets = target_manager.get_by_platform(platform) + for target in targets: + result = target.run_integration_test( + plugin_name, example_dir, timeout) + test_results.append(result) finally: subprocess.run('flutter-tizen clean', shell=True, From 04c13d007fee85e8ca011533deaca7a657aa3c6e Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Thu, 30 Sep 2021 17:36:12 +0900 Subject: [PATCH 02/11] Implement ephmeral target features --- tools/commands/integration_test.py | 145 ++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 4 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index d64cec977..7a6a287b0 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -54,6 +54,7 @@ class Target: def __init__(self, name, platform, id=None): self.name = name self.platform = platform + self.device_profile, self.tizen_version = platform.split('-') self.id = id self.target_tuple = (self.platform, self.name, self.id) @@ -133,6 +134,92 @@ def run_integration_test(self, plugin_name, directory, timeout): ]) +class EphemeralTarget(Target): + + def __init__(self, name, platform): + super().__init__(name, platform) + + def run_integration_test(self, plugin_name, directory, timeout): + if self.device_profile == 'tv': + return TestResult.fail(plugin_name, self.target_tuple, [ + 'Running integration test for ephemeral tv targets is not supported.' + ]) + self.launch() + result = super().run_integration_test(plugin_name, directory, timeout) + self.power_off() + return result + + def _find_id(self): + completed_process = subprocess.run('sdb devices', + shell=True, + cwd='.', + universal_newlines=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + if completed_process.returncode != 0: + raise Exception('sdb failure.') + + lines = completed_process.stdout.rstrip().split('\n') + for line in lines[1:]: + tokens = re.split('[\t ]', line) + id = tokens[0] + name = tokens[-1] + if name == self.name: + return id + raise Exception(f'Could not find connected target {self.name}') + + def launch(self): + completed_process = subprocess.run(f'em-cli launch -n {self.name}', + shell=True, + cwd='.') + if completed_process.returncode != 0: + raise Exception(f'Target {self.name} launch failed.') + time.sleep(5) + self.id = self._find_id() + pass + + def power_off(self): + completed_process = subprocess.run(f'sdb -s {self.id} root on', + shell=True, + cwd='.', + stdout=open(os.devnull, 'wb')) + if completed_process.returncode != 0: + raise Exception(f'Target {self.id} power off failed.') + completed_process = subprocess.run(f'sdb -s {self.id} shell poweroff', + shell=True, + cwd='.') + if completed_process.returncode != 0: + raise Exception(f'Target {self.id} power off failed.') + time.sleep(1) + self.id = None + + def create(self): + completed_process = subprocess.run( + f'em-cli create -n {self.name} -p {self._get_tizensdk_platform()}', + shell=True, + cwd='.') + + def delete(self): + completed_process = subprocess.run(f'em-cli delete -n {self.name}', + shell=True, + cwd='.') + if completed_process.returncode != 0: + raise Exception(f'Target {self.name} deletion failed.') + + def _get_tizensdk_platform(self): + """Gets the platform name that's understood by the Tizen sdk's em-cli command.""" + if self.device_profile == 'wearable': + return f'{self.platform}-circle-x86' + elif self.device_profile == 'tv': + return f'tv-samsung-{self.tizen_version}-x86' + elif self.device_profile == 'mobile': + return f'{self.platform}-x86' + else: + raise Exception( + f'Test target must start with wearable, mobile, or tv. {self.platform} is an unknown test target.' + ) + + class TargetManager: """A manager class that finds and keep tracks of Tizen targets.""" @@ -214,6 +301,33 @@ def _parse_target_info(self, capability_info): return device_profile, tizen_version +class EphemeralTargetManager(TargetManager): + + def __init__(self, platforms): + self.platforms = platforms + super().__init__() + + def __enter__(self): + for platform in self.platforms: + self._create_ephemeral_target(platform) + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._delete_ephemeral_targets() + + def _create_ephemeral_target(self, platform): + device_profile = platform.split('-')[0] + target_name = f'{device_profile}-{os.getpid()}' + target = EphemeralTarget(target_name, platform) + target.create() + self.targets_per_platform[platform].append(target) + + def _delete_ephemeral_targets(self): + for targets in self.targets_per_platform.values(): + for target in targets: + target.delete() + + class TestResult: """A class that specifies the result of a plugin integration test. @@ -276,9 +390,23 @@ def set_subparser(subparsers): b: [mobile-6.0] c: [wearable-4.0] )''') + parser.add_argument( + '--use-ephemeral-targets', + default=False, + action='store_true', + help='''Create and destroy ephemeral targets during test. +Must provide --platforms to specify which platform targets to create. +Otherwise the option is ignored.''') + + +def _get_target_manager(use_ephemeral_targets, platforms): + if use_ephemeral_targets: + return EphemeralTargetManager(platforms) + else: + return TargetManager() -def _integration_test(plugin_dir, platforms, timeout): +def _integration_test(plugin_dir, platforms, timeout, use_ephemeral_targets): """Runs integration test in the example package for plugin_dir Currently the tools assumes that there's only one example package per plugin. @@ -287,6 +415,8 @@ def _integration_test(plugin_dir, platforms, timeout): plugin_dir (str): The path to a single plugin directory. platforms (List[str]): A list of testing platforms. timeout (int): Time limit in seconds before cancelling the test. + use_ephemeral_targets (bool): Whether to create and delete targets + for test. Returns: TestResult: The result of the plugin integration test. @@ -325,7 +455,8 @@ def _integration_test(plugin_dir, platforms, timeout): ] try: - with TargetManager() as target_manager: + with _get_target_manager(use_ephemeral_targets, + platforms) as target_manager: if not platforms: platforms.extend(target_manager.platforms()) if not platforms: @@ -386,6 +517,11 @@ def run_integration_test(args): f'The recipe file {args.recipe} is not a valid yaml file.') exit(1) + # --use-ephemeral-targets option is ignored if not used + # with --platforms option. + if args.use_ephemeral_targets and not args.platforms: + args.use_ephemeral_targets = False + packages_dir = command_utils.get_package_dir() testing_plugins, excluded_plugins = command_utils.get_target_plugins( packages_dir, @@ -407,8 +543,9 @@ def run_integration_test(args): platforms = platforms_per_plugin[testing_plugin] results.extend( - _integration_test(os.path.join(packages_dir, testing_plugin), - platforms, args.timeout)) + _integration_test(os.path.join(packages_dir, + testing_plugin), platforms, + args.timeout, args.use_ephemeral_targets)) print(f'============= TEST RESULT =============') failed_plugins = [] From 63bb4ba94b559ff0b2137743d1e73c858d9f116a Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 12:15:11 +0900 Subject: [PATCH 03/11] Clean ups - Improve/update/add/move documentation. - Remove legacy class member of `TargetManager`, `target_per_id`. - Raise exception in `EphemeralTarget.create()` on failure. - Add trailing commas in some parameter lists for readability. - Rename `TargetManager.platforms()` to `TargetManager.get_platforms()`. - Call `super().__init__()` first in `EphemeralTargetManager.__init__()`. - Call `super().__exit__()` in `EphemeralTargetManager.__exit__()`. --- tools/commands/integration_test.py | 79 +++++++++++++++--------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 7a6a287b0..3a185465e 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -1,5 +1,6 @@ """A subcommand that runs integration tests for multiple Tizen plugins -under packages. +under packages. By default, the tool will run integration tests of all plugins +under packages on all connected targets. Here are some of the common use cases. Assume packages contain plugins a, b, c, d, and e. @@ -9,24 +10,8 @@ tools/run_command.py test --exclude a b c # runs d, e - exclude has precedence: tools/run_command.py test --plugins a b c --exclude b # runs a c - -By default, the tool will run integration tests on all connected targets. -A target is a Tizen device (either a physical device or an emulator) that can -run Flutter applications. Generally targets are connected when device is -connected to the host PC either with a cable or wirelessly for physical device, -and when launching targets from Tizen SDK's Emulator Manager for emulators. - -Each target has three important pieces of information: -- name: the name of the target. -- platform: defines the device profile and Tizen version, usually expressed in -- (ex: wearable-5.5, tv-6.0). -- id: the identifier assigned to a **connected** target. - -Here are some of the use cases where the information can be useful: -- testing all targets that satisfies platform: -tools/run_command.py test --platform wearable-5.5 -- testing target with id: -tools/run_command.py test --id some_id +- testing on all targets that satisfies wearable-5.5 platform: +tools/run_command.py test --platforms wearable-5.5 """ import os @@ -49,7 +34,19 @@ class Target: - """A Tizen device that can run Flutter applications.""" + """A Tizen device that can run Flutter applications. + + A target is a Tizen device (either a physical device or an emulator) that can + run Flutter applications. Generally targets are connected when physical devices + are connected to the host PC either with a cable or wirelessly, and when + emulators are launched by Tizen SDK's Emulator Manager. + + Each target has three important pieces of information: + - name: the name of the target. + - platform: defines the device profile and Tizen version, expressed in + - (ex: wearable-5.5, tv-6.0). + - id: the identifier assigned to a **connected** target. + """ def __init__(self, name, platform, id=None): self.name = name @@ -129,12 +126,12 @@ def run_integration_test(self, plugin_name, directory, timeout): else: # match.group(2) == 'Some tests failed.' return TestResult.fail(plugin_name, self.target_tuple, [ - 'flutter-tizen test integration_test failed, \ - see the output above for details.' + 'flutter-tizen test integration_test failed, see the output above for details.' ]) class EphemeralTarget(Target): + """A Tizen emulator that launches/poweroffs itself during test.""" def __init__(self, name, platform): super().__init__(name, platform) @@ -198,6 +195,8 @@ def create(self): f'em-cli create -n {self.name} -p {self._get_tizensdk_platform()}', shell=True, cwd='.') + if completed_process.returncode != 0: + raise Exception(f'Target {self.name} creation failed.') def delete(self): completed_process = subprocess.run(f'em-cli delete -n {self.name}', @@ -221,10 +220,9 @@ def _get_tizensdk_platform(self): class TargetManager: - """A manager class that finds and keep tracks of Tizen targets.""" + """A manager class that finds and manages a collection of Tizen targets.""" def __init__(self): - self.target_per_id = {} self.targets_per_platform = defaultdict(list) def __enter__(self): @@ -232,22 +230,15 @@ def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): - self.target_per_id.clear() self.targets_per_platform.clear() def exists_platform(self, platform): return len(self.targets_per_platform[platform]) > 0 - def exists_id(self, id): - return id in self.target_per_id - def get_by_platform(self, platform): return self.targets_per_platform[platform] - def get_by_id(self, id): - return self.target_per_id[id] - - def platforms(self): + def get_platforms(self): return self.targets_per_platform.keys() def _find_all_targets(self): @@ -285,7 +276,6 @@ def _find_all_targets(self): platform = f'{device_profile}-{tizen_version}' target = Target(name, platform, id) - self.target_per_id[id] = target self.targets_per_platform[platform].append(target) def _parse_target_info(self, capability_info): @@ -302,10 +292,11 @@ def _parse_target_info(self, capability_info): class EphemeralTargetManager(TargetManager): + """A TargetManager for EphemeralTargets.""" def __init__(self, platforms): - self.platforms = platforms super().__init__() + self.platforms = platforms def __enter__(self): for platform in self.platforms: @@ -314,6 +305,7 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): self._delete_ephemeral_targets() + super().__exit__(exc_type, exc_value, traceback) def _create_ephemeral_target(self, platform): device_profile = platform.split('-')[0] @@ -332,7 +324,9 @@ class TestResult: """A class that specifies the result of a plugin integration test. Attributes: + plugin_name: The name of the tested plugin. run_state: The result of the test. Can be either succeeded for failed. + target: Information of the target that plugin was tested on. details: A list of details about the test result. (e.g. reasons for failure.) """ @@ -455,10 +449,12 @@ def _integration_test(plugin_dir, platforms, timeout, use_ephemeral_targets): ] try: - with _get_target_manager(use_ephemeral_targets, - platforms) as target_manager: + with _get_target_manager( + use_ephemeral_targets, + platforms, + ) as target_manager: if not platforms: - platforms.extend(target_manager.platforms()) + platforms.extend(target_manager.get_platforms()) if not platforms: return [ TestResult.fail( @@ -543,9 +539,12 @@ def run_integration_test(args): platforms = platforms_per_plugin[testing_plugin] results.extend( - _integration_test(os.path.join(packages_dir, - testing_plugin), platforms, - args.timeout, args.use_ephemeral_targets)) + _integration_test( + os.path.join(packages_dir, testing_plugin), + platforms, + args.timeout, + args.use_ephemeral_targets, + )) print(f'============= TEST RESULT =============') failed_plugins = [] From 47dc440672873b7923d9dd6dd3b39d374f82068e Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 12:29:54 +0900 Subject: [PATCH 04/11] Validate --platforms option Also: - Exit tool if --use-ephemeral-targets option is not used with --platforms. - Include Tizen version in created ephemeral target name. --- tools/commands/integration_test.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 3a185465e..d3ac4744d 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -51,7 +51,7 @@ class Target: def __init__(self, name, platform, id=None): self.name = name self.platform = platform - self.device_profile, self.tizen_version = platform.split('-') + self.device_profile, self.tizen_version = platform.split('-', 1) self.id = id self.target_tuple = (self.platform, self.name, self.id) @@ -308,8 +308,7 @@ def __exit__(self, exc_type, exc_value, traceback): super().__exit__(exc_type, exc_value, traceback) def _create_ephemeral_target(self, platform): - device_profile = platform.split('-')[0] - target_name = f'{device_profile}-{os.getpid()}' + target_name = f'{platform}-{os.getpid()}' target = EphemeralTarget(target_name, platform) target.create() self.targets_per_platform[platform].append(target) @@ -389,8 +388,7 @@ def set_subparser(subparsers): default=False, action='store_true', help='''Create and destroy ephemeral targets during test. -Must provide --platforms to specify which platform targets to create. -Otherwise the option is ignored.''') +Must provide --platforms to specify which platform targets to create.''') def _get_target_manager(use_ephemeral_targets, platforms): @@ -513,10 +511,19 @@ def run_integration_test(args): f'The recipe file {args.recipe} is not a valid yaml file.') exit(1) - # --use-ephemeral-targets option is ignored if not used - # with --platforms option. + if args.platforms: + for platform in args.platforms: + if '-' not in platform: + print( + f'inputs of --platforms must be - format, {platform}.' + ) + exit(1) + if args.use_ephemeral_targets and not args.platforms: - args.use_ephemeral_targets = False + print( + '--use-ephemeral-targets option must be used with --platforms option.' + ) + exit(1) packages_dir = command_utils.get_package_dir() testing_plugins, excluded_plugins = command_utils.get_target_plugins( From fb998defcf2d8f38ceca258c1f18d404fd67a9a9 Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 14:19:05 +0900 Subject: [PATCH 05/11] Support tv profile Also: - Use valid characters for target name. --- tools/commands/integration_test.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index d3ac4744d..19bf8f534 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -137,10 +137,6 @@ def __init__(self, name, platform): super().__init__(name, platform) def run_integration_test(self, plugin_name, directory, timeout): - if self.device_profile == 'tv': - return TestResult.fail(plugin_name, self.target_tuple, [ - 'Running integration test for ephemeral tv targets is not supported.' - ]) self.launch() result = super().run_integration_test(plugin_name, directory, timeout) self.power_off() @@ -176,6 +172,27 @@ def launch(self): pass def power_off(self): + # The poweroff logic for tv is different because sdb root is not allowed. + if self.device_profile == 'tv': + completed_process = subprocess.run( + f'ps a | grep emulator-x86_64 | grep {self.name}', + shell=True, + cwd='.', + universal_newlines=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + if completed_process.returncode != 0: + raise Exception(f'Target {self.id} power off failed.') + pid = completed_process.stdout.strip().split(' ')[0] + completed_process = subprocess.run(f'kill -9 {pid}', + shell=True, + cwd='.', + stdout=open(os.devnull, 'wb')) + if completed_process.returncode != 0: + raise Exception(f'Target {self.id} power off failed.') + time.sleep(1) + self.id = None + return completed_process = subprocess.run(f'sdb -s {self.id} root on', shell=True, cwd='.', @@ -308,7 +325,9 @@ def __exit__(self, exc_type, exc_value, traceback): super().__exit__(exc_type, exc_value, traceback) def _create_ephemeral_target(self, platform): - target_name = f'{platform}-{os.getpid()}' + device_profile, tizen_version = platform.split('-', 1) + # Target name valid characters are [A-Za-z0-9-_]. + target_name = f'{device_profile}-{tizen_version.replace(".", "_")}-{os.getpid()}' target = EphemeralTarget(target_name, platform) target.create() self.targets_per_platform[platform].append(target) From 2bee0a0fa4f21ca3281e4f4169bbce9e540b3b5f Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 14:27:06 +0900 Subject: [PATCH 06/11] Remove if statement in choosing subcommands Also - Group imports from same module. --- tools/commands/build_example.py | 1 + tools/commands/check_tidy.py | 8 +++++--- tools/commands/integration_test.py | 1 + tools/commands/print_plugins.py | 1 + tools/run_command.py | 19 +++++++------------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/tools/commands/build_example.py b/tools/commands/build_example.py index 1c0fb4423..5b79b3f97 100755 --- a/tools/commands/build_example.py +++ b/tools/commands/build_example.py @@ -12,6 +12,7 @@ def set_subparser(subparsers): run_on_changed_packages=True, base_sha=True, command='build') + parser.set_defaults(func=run_build_examples) def _build_examples(plugin_dir): diff --git a/tools/commands/check_tidy.py b/tools/commands/check_tidy.py index a304b2b78..67c388036 100755 --- a/tools/commands/check_tidy.py +++ b/tools/commands/check_tidy.py @@ -36,6 +36,7 @@ class Stats(object): + def __init__(self): self.files = 0 self.lines = 0 @@ -56,7 +57,8 @@ def _run_check_tidy(src_dir, update, clang_format, stats): continue for file in [ - join(dirpath, name) for name in filenames + join(dirpath, name) + for name in filenames if is_checked_by_clang(name) ]: @@ -87,8 +89,7 @@ def report_error(msg, line=None): if line.endswith(' \n') or line.endswith('\t\n'): report_error('trailing whitespace', lineno) if not line.endswith('\n'): - report_error('line ends without NEW LINE character', - lineno) + report_error('line ends without NEW LINE character', lineno) if not line.strip(): stats.empty_lines += 1 @@ -116,6 +117,7 @@ def set_subparser(subparsers): metavar='PATH', default=DEFAULT_DIR, help='directory to process (default: %(default)s)') + parser.set_defaults(func=run_check_tidy) def run_check_tidy(args): diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 19bf8f534..ee93acf0f 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -408,6 +408,7 @@ def set_subparser(subparsers): action='store_true', help='''Create and destroy ephemeral targets during test. Must provide --platforms to specify which platform targets to create.''') + parser.set_defaults(func=run_integration_test) def _get_target_manager(use_ephemeral_targets, platforms): diff --git a/tools/commands/print_plugins.py b/tools/commands/print_plugins.py index c7fd0acf5..141d28556 100755 --- a/tools/commands/print_plugins.py +++ b/tools/commands/print_plugins.py @@ -7,6 +7,7 @@ def set_subparser(subparsers): run_on_changed_packages=True, base_sha=True, command='print plugins') + parser.set_defaults(func=run_print_plugins) def run_print_plugins(args): diff --git a/tools/run_command.py b/tools/run_command.py index 7e526b366..b294ef8f7 100755 --- a/tools/run_command.py +++ b/tools/run_command.py @@ -3,10 +3,12 @@ import sys import argparse -from commands import check_tidy -from commands import integration_test -from commands import build_example -from commands import print_plugins +from commands import ( + check_tidy, + integration_test, + build_example, + print_plugins, +) if __name__ == "__main__": parser = argparse.ArgumentParser() @@ -21,11 +23,4 @@ parser.print_help() exit(1) - if args.subcommand == 'tidy': - check_tidy.run_check_tidy(args) - elif args.subcommand == 'test': - integration_test.run_integration_test(args) - elif args.subcommand == 'build': - build_example.run_build_examples(args) - elif args.subcommand == 'plugins': - print_plugins.run_print_plugins(args) + args.func(args) From bd630aeb76d9f741f3bcc5b77acaee4278ac656d Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 14:49:29 +0900 Subject: [PATCH 07/11] Document dependent executables info --- tools/run_command.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/run_command.py b/tools/run_command.py index b294ef8f7..cd5801163 100755 --- a/tools/run_command.py +++ b/tools/run_command.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +"""CLI tool that manages multiple plugin packages in this repository. +It is mainly intended to run in CI systems. Each subcommand has the following +dependent executables and the system must be able to find their correct paths. + +- check_tidy: clang-format-11 +- integration_test: flutter-tizen, sdb, em-cli +- build_example: flutter-tizen +""" import sys import argparse From 00efa40ba3a9da8a48e71e7069f6b7ab38f48c32 Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Fri, 1 Oct 2021 17:23:11 +0900 Subject: [PATCH 08/11] Allow either --platforms or --recipe with --use-ephermeral-targets. Also: - Make --platforms and --recipe options mutually exclusive. --- tools/commands/integration_test.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index ee93acf0f..72259559e 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -387,10 +387,10 @@ def set_subparser(subparsers): The selected targets will be used for all plugins, if you wish to run different targets for each plugin, use the --recipe option instead. ''') - parser.add_argument('--recipe', - type=str, - default='', - help='''The recipe file path. A recipe refers to a + group.add_argument('--recipe', + type=str, + default='', + help='''The recipe file path. A recipe refers to a yaml file that defines a list of target platforms to test for each plugin. Pass this file if you want to select specific target platform for different plugins. Note that recipe does not select which plugins to test(that is covered @@ -407,7 +407,8 @@ def set_subparser(subparsers): default=False, action='store_true', help='''Create and destroy ephemeral targets during test. -Must provide --platforms to specify which platform targets to create.''') +Must provide --platforms or --recipe option to specify which +platform targets to create.''') parser.set_defaults(func=run_integration_test) @@ -477,7 +478,7 @@ def _integration_test(plugin_dir, platforms, timeout, use_ephemeral_targets): return [ TestResult.fail( plugin_name, - errors=['Cannot find any connected targets.']) + errors=['Cannot find any testable targets.']) ] errors = [] completed_process = subprocess.run('flutter-tizen pub get', @@ -539,9 +540,9 @@ def run_integration_test(args): ) exit(1) - if args.use_ephemeral_targets and not args.platforms: + if args.use_ephemeral_targets and not args.platforms and not args.recipe: print( - '--use-ephemeral-targets option must be used with --platforms option.' + '--use-ephemeral-targets option must be used with either --platforms or --recipe option.' ) exit(1) From 1c764db8085cce32dc46f458adf602d7b65edc10 Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Tue, 5 Oct 2021 13:39:09 +0900 Subject: [PATCH 09/11] Update based on review --- tools/commands/integration_test.py | 68 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 72259559e..132ddfabd 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -154,11 +154,9 @@ def _find_id(self): lines = completed_process.stdout.rstrip().split('\n') for line in lines[1:]: - tokens = re.split('[\t ]', line) - id = tokens[0] - name = tokens[-1] - if name == self.name: - return id + tokens = re.split('\s+', line) + if tokens[-1] == self.name: + return tokens[0] raise Exception(f'Could not find connected target {self.name}') def launch(self): @@ -167,45 +165,45 @@ def launch(self): cwd='.') if completed_process.returncode != 0: raise Exception(f'Target {self.name} launch failed.') + # There's no straightforward way to know when the target is fully + # launched. The current setting is based on some testing. time.sleep(5) self.id = self._find_id() - pass def power_off(self): # The poweroff logic for tv is different because sdb root is not allowed. if self.device_profile == 'tv': - completed_process = subprocess.run( - f'ps a | grep emulator-x86_64 | grep {self.name}', - shell=True, - cwd='.', - universal_newlines=True, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE) - if completed_process.returncode != 0: - raise Exception(f'Target {self.id} power off failed.') - pid = completed_process.stdout.strip().split(' ')[0] - completed_process = subprocess.run(f'kill -9 {pid}', - shell=True, - cwd='.', - stdout=open(os.devnull, 'wb')) - if completed_process.returncode != 0: + try: + stdout = subprocess.check_output( + f'ps a | grep emulator-x86_64 | grep {self.name}', + shell=True, + cwd='.', + universal_newlines=True) + pid = stdout.strip().split(' ')[0] + subprocess.run(f'kill -9 {pid}', + shell=True, + check=True, + cwd='.', + stdout=open(os.devnull, 'wb')) + time.sleep(1) + self.id = None + except subprocess.CalledProcessError: raise Exception(f'Target {self.id} power off failed.') + return + try: + subprocess.run(f'sdb -s {self.id} root on', + shell=True, + check=True, + cwd='.', + stdout=open(os.devnull, 'wb')) + subprocess.run(f'sdb -s {self.id} shell poweroff', + shell=True, + check=True, + cwd='.') time.sleep(1) self.id = None - return - completed_process = subprocess.run(f'sdb -s {self.id} root on', - shell=True, - cwd='.', - stdout=open(os.devnull, 'wb')) - if completed_process.returncode != 0: - raise Exception(f'Target {self.id} power off failed.') - completed_process = subprocess.run(f'sdb -s {self.id} shell poweroff', - shell=True, - cwd='.') - if completed_process.returncode != 0: + except subprocess.CalledProcessError: raise Exception(f'Target {self.id} power off failed.') - time.sleep(1) - self.id = None def create(self): completed_process = subprocess.run( @@ -270,7 +268,7 @@ def _find_all_targets(self): lines = completed_process.stdout.rstrip().split('\n') for line in lines[1:]: - tokens = re.split('[\t ]', line) + tokens = re.split('\s+', line) id = tokens[0] name = tokens[-1] completed_process = subprocess.run(f'sdb -s {id} capability', From acb32a2c3d125151e7b3b883b367ea9ad0d04edc Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Tue, 5 Oct 2021 14:19:03 +0900 Subject: [PATCH 10/11] Store pid in launch and use it in poweroff Also: - Use kill command for powering off targets for all device profiles. --- tools/commands/integration_test.py | 57 +++++++++++++----------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index 132ddfabd..cfa35e544 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -135,6 +135,7 @@ class EphemeralTarget(Target): def __init__(self, name, platform): super().__init__(name, platform) + self._pid = None def run_integration_test(self, plugin_name, directory, timeout): self.launch() @@ -159,6 +160,19 @@ def _find_id(self): return tokens[0] raise Exception(f'Could not find connected target {self.name}') + def _find_pid(self): + completed_process = subprocess.run( + f'ps a | grep emulator-x86_64 | grep {self.name}', + shell=True, + cwd='.', + universal_newlines=True, + stderr=subprocess.PIPE, + stdout=subprocess.PIPE) + if completed_process.returncode != 0: + raise Exception(f'Could not find pid of target {self.name}') + pid = completed_process.stdout.strip().split(' ')[0] + return pid + def launch(self): completed_process = subprocess.run(f'em-cli launch -n {self.name}', shell=True, @@ -166,44 +180,21 @@ def launch(self): if completed_process.returncode != 0: raise Exception(f'Target {self.name} launch failed.') # There's no straightforward way to know when the target is fully - # launched. The current setting is based on some testing. + # launched. The current sleep setting is based on some testing. time.sleep(5) self.id = self._find_id() + self._pid = self._find_pid() def power_off(self): - # The poweroff logic for tv is different because sdb root is not allowed. - if self.device_profile == 'tv': - try: - stdout = subprocess.check_output( - f'ps a | grep emulator-x86_64 | grep {self.name}', - shell=True, - cwd='.', - universal_newlines=True) - pid = stdout.strip().split(' ')[0] - subprocess.run(f'kill -9 {pid}', - shell=True, - check=True, - cwd='.', - stdout=open(os.devnull, 'wb')) - time.sleep(1) - self.id = None - except subprocess.CalledProcessError: - raise Exception(f'Target {self.id} power off failed.') - return - try: - subprocess.run(f'sdb -s {self.id} root on', - shell=True, - check=True, - cwd='.', - stdout=open(os.devnull, 'wb')) - subprocess.run(f'sdb -s {self.id} shell poweroff', - shell=True, - check=True, - cwd='.') - time.sleep(1) - self.id = None - except subprocess.CalledProcessError: + completed_process = subprocess.run(f'kill -9 {self._pid}', + shell=True, + cwd='.', + stdout=open(os.devnull, 'wb')) + if completed_process.returncode != 0: raise Exception(f'Target {self.id} power off failed.') + time.sleep(1) + self.id = None + self.pid = None def create(self): completed_process = subprocess.run( From dbc1b672a7f5364c6ed0dc47a0ffdf9780ca9b7a Mon Sep 17 00:00:00 2001 From: Hakkyu Kim Date: Tue, 5 Oct 2021 14:22:40 +0900 Subject: [PATCH 11/11] Remove unnecessry cwd argument in some places --- tools/commands/integration_test.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/tools/commands/integration_test.py b/tools/commands/integration_test.py index cfa35e544..e95ac5da2 100755 --- a/tools/commands/integration_test.py +++ b/tools/commands/integration_test.py @@ -146,7 +146,6 @@ def run_integration_test(self, plugin_name, directory, timeout): def _find_id(self): completed_process = subprocess.run('sdb devices', shell=True, - cwd='.', universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @@ -164,7 +163,6 @@ def _find_pid(self): completed_process = subprocess.run( f'ps a | grep emulator-x86_64 | grep {self.name}', shell=True, - cwd='.', universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @@ -175,8 +173,7 @@ def _find_pid(self): def launch(self): completed_process = subprocess.run(f'em-cli launch -n {self.name}', - shell=True, - cwd='.') + shell=True) if completed_process.returncode != 0: raise Exception(f'Target {self.name} launch failed.') # There's no straightforward way to know when the target is fully @@ -188,7 +185,6 @@ def launch(self): def power_off(self): completed_process = subprocess.run(f'kill -9 {self._pid}', shell=True, - cwd='.', stdout=open(os.devnull, 'wb')) if completed_process.returncode != 0: raise Exception(f'Target {self.id} power off failed.') @@ -199,15 +195,13 @@ def power_off(self): def create(self): completed_process = subprocess.run( f'em-cli create -n {self.name} -p {self._get_tizensdk_platform()}', - shell=True, - cwd='.') + shell=True) if completed_process.returncode != 0: raise Exception(f'Target {self.name} creation failed.') def delete(self): completed_process = subprocess.run(f'em-cli delete -n {self.name}', - shell=True, - cwd='.') + shell=True) if completed_process.returncode != 0: raise Exception(f'Target {self.name} deletion failed.') @@ -250,7 +244,6 @@ def get_platforms(self): def _find_all_targets(self): completed_process = subprocess.run('sdb devices', shell=True, - cwd='.', universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) @@ -264,7 +257,6 @@ def _find_all_targets(self): name = tokens[-1] completed_process = subprocess.run(f'sdb -s {id} capability', shell=True, - cwd='.', universal_newlines=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE)