| OLD | NEW |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Test runners for iOS.""" | 5 """Test runners for iOS.""" |
| 6 | 6 |
| 7 import argparse | 7 import argparse |
| 8 import collections | 8 import collections |
| 9 import errno | 9 import errno |
| 10 import os | 10 import os |
| 11 import shutil | 11 import shutil |
| 12 import subprocess | 12 import subprocess |
| 13 import sys | 13 import sys |
| 14 import tempfile | 14 import tempfile |
| 15 import time | 15 import time |
| 16 | 16 |
| 17 import find_xcode | 17 import find_xcode |
| 18 import gtest_utils | 18 import gtest_utils |
| 19 import xctest_utils | 19 import xctest_utils |
| 20 | 20 |
| 21 | 21 |
| 22 DERIVED_DATA = os.path.expanduser('~/Library/Developer/Xcode/DerivedData') |
| 23 |
| 24 |
| 22 XCTEST_PROJECT = os.path.abspath(os.path.join( | 25 XCTEST_PROJECT = os.path.abspath(os.path.join( |
| 23 os.path.dirname(__file__), | 26 os.path.dirname(__file__), |
| 24 'TestProject', | 27 'TestProject', |
| 25 'TestProject.xcodeproj', | 28 'TestProject.xcodeproj', |
| 26 )) | 29 )) |
| 27 | 30 |
| 28 XCTEST_SCHEME = 'TestProject' | 31 XCTEST_SCHEME = 'TestProject' |
| 29 | 32 |
| 30 | 33 |
| 31 class Error(Exception): | 34 class Error(Exception): |
| (...skipping 191 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 223 """Performs cleanup actions which must occur after every test launch.""" | 226 """Performs cleanup actions which must occur after every test launch.""" |
| 224 raise NotImplementedError | 227 raise NotImplementedError |
| 225 | 228 |
| 226 def screenshot_desktop(self): | 229 def screenshot_desktop(self): |
| 227 """Saves a screenshot of the desktop in the output directory.""" | 230 """Saves a screenshot of the desktop in the output directory.""" |
| 228 subprocess.check_call([ | 231 subprocess.check_call([ |
| 229 'screencapture', | 232 'screencapture', |
| 230 os.path.join(self.out_dir, 'desktop_%s.png' % time.time()), | 233 os.path.join(self.out_dir, 'desktop_%s.png' % time.time()), |
| 231 ]) | 234 ]) |
| 232 | 235 |
| 236 def retrieve_derived_data(self): |
| 237 """Retrieves the contents of DerivedData""" |
| 238 # DerivedData contains some logs inside workspace-specific directories. |
| 239 # Since we don't control the name of the workspace or project, most of |
| 240 # the directories are just called "temporary", making it hard to tell |
| 241 # which directory we need to retrieve. Instead we just delete the |
| 242 # entire contents of this directory before starting and return the |
| 243 # entire contents after the test is over. |
| 244 if os.path.exists(DERIVED_DATA): |
| 245 os.mkdir(os.path.join(self.out_dir, 'DerivedData')) |
| 246 derived_data = os.path.join(self.out_dir, 'DerivedData') |
| 247 for directory in os.listdir(DERIVED_DATA): |
| 248 shutil.move(os.path.join(DERIVED_DATA, directory), derived_data) |
| 249 |
| 250 def wipe_derived_data(self): |
| 251 """Removes the contents of Xcode's DerivedData directory.""" |
| 252 if os.path.exists(DERIVED_DATA): |
| 253 shutil.rmtree(DERIVED_DATA) |
| 254 os.mkdir(DERIVED_DATA) |
| 255 |
| 233 def _run(self, cmd): | 256 def _run(self, cmd): |
| 234 """Runs the specified command, parsing GTest output. | 257 """Runs the specified command, parsing GTest output. |
| 235 | 258 |
| 236 Args: | 259 Args: |
| 237 cmd: List of strings forming the command to run. | 260 cmd: List of strings forming the command to run. |
| 238 | 261 |
| 239 Returns: | 262 Returns: |
| 240 GTestResult instance. | 263 GTestResult instance. |
| 241 """ | 264 """ |
| 242 print ' '.join(cmd) | 265 print ' '.join(cmd) |
| (...skipping 186 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 429 self.iossim_path, | 452 self.iossim_path, |
| 430 '-d', self.platform, | 453 '-d', self.platform, |
| 431 '-p', | 454 '-p', |
| 432 '-s', self.version, | 455 '-s', self.version, |
| 433 ]).rstrip() | 456 ]).rstrip() |
| 434 | 457 |
| 435 def set_up(self): | 458 def set_up(self): |
| 436 """Performs setup actions which must occur prior to every test launch.""" | 459 """Performs setup actions which must occur prior to every test launch.""" |
| 437 self.kill_simulators() | 460 self.kill_simulators() |
| 438 self.wipe_simulator() | 461 self.wipe_simulator() |
| 462 self.wipe_derived_data() |
| 439 self.homedir = self.get_home_directory() | 463 self.homedir = self.get_home_directory() |
| 440 # Crash reports have a timestamp in their file name, formatted as | 464 # Crash reports have a timestamp in their file name, formatted as |
| 441 # YYYY-MM-DD-HHMMSS. Save the current time in the same format so | 465 # YYYY-MM-DD-HHMMSS. Save the current time in the same format so |
| 442 # we can compare and fetch crash reports from this run later on. | 466 # we can compare and fetch crash reports from this run later on. |
| 443 self.start_time = time.strftime('%Y-%m-%d-%H%M%S', time.localtime()) | 467 self.start_time = time.strftime('%Y-%m-%d-%H%M%S', time.localtime()) |
| 444 | 468 |
| 445 def extract_test_data(self): | 469 def extract_test_data(self): |
| 446 """Extracts data emitted by the test.""" | 470 """Extracts data emitted by the test.""" |
| 447 # Find the Documents directory of the test app. The app directory names | 471 # Find the Documents directory of the test app. The app directory names |
| 448 # don't correspond with any known information, so we have to examine them | 472 # don't correspond with any known information, so we have to examine them |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 486 # a staight string comparison works. | 510 # a staight string comparison works. |
| 487 if report_time > self.start_time: | 511 if report_time > self.start_time: |
| 488 with open(os.path.join(crash_reports_dir, crash_report)) as f: | 512 with open(os.path.join(crash_reports_dir, crash_report)) as f: |
| 489 self.logs['crash report (%s)' % report_time] = ( | 513 self.logs['crash report (%s)' % report_time] = ( |
| 490 f.read().splitlines()) | 514 f.read().splitlines()) |
| 491 | 515 |
| 492 def tear_down(self): | 516 def tear_down(self): |
| 493 """Performs cleanup actions which must occur after every test launch.""" | 517 """Performs cleanup actions which must occur after every test launch.""" |
| 494 self.extract_test_data() | 518 self.extract_test_data() |
| 495 self.retrieve_crash_reports() | 519 self.retrieve_crash_reports() |
| 520 self.retrieve_derived_data() |
| 496 self.screenshot_desktop() | 521 self.screenshot_desktop() |
| 497 self.kill_simulators() | 522 self.kill_simulators() |
| 498 self.wipe_simulator() | 523 self.wipe_simulator() |
| 499 if os.path.exists(self.homedir): | 524 if os.path.exists(self.homedir): |
| 500 shutil.rmtree(self.homedir, ignore_errors=True) | 525 shutil.rmtree(self.homedir, ignore_errors=True) |
| 501 self.homedir = '' | 526 self.homedir = '' |
| 502 | 527 |
| 503 def get_launch_command(self, test_filter=None, invert=False): | 528 def get_launch_command(self, test_filter=None, invert=False): |
| 504 """Returns the command that can be used to launch the test app. | 529 """Returns the command that can be used to launch the test app. |
| 505 | 530 |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 596 ['ideviceinstaller', '--udid', self.udid, '--uninstall', app]) | 621 ['ideviceinstaller', '--udid', self.udid, '--uninstall', app]) |
| 597 | 622 |
| 598 def install_app(self): | 623 def install_app(self): |
| 599 """Installs the app.""" | 624 """Installs the app.""" |
| 600 subprocess.check_call( | 625 subprocess.check_call( |
| 601 ['ideviceinstaller', '--udid', self.udid, '--install', self.app_path]) | 626 ['ideviceinstaller', '--udid', self.udid, '--install', self.app_path]) |
| 602 | 627 |
| 603 def set_up(self): | 628 def set_up(self): |
| 604 """Performs setup actions which must occur prior to every test launch.""" | 629 """Performs setup actions which must occur prior to every test launch.""" |
| 605 self.uninstall_apps() | 630 self.uninstall_apps() |
| 631 self.wipe_derived_data() |
| 606 self.install_app() | 632 self.install_app() |
| 607 | 633 |
| 608 def extract_test_data(self): | 634 def extract_test_data(self): |
| 609 """Extracts data emitted by the test.""" | 635 """Extracts data emitted by the test.""" |
| 610 try: | 636 try: |
| 611 subprocess.check_call([ | 637 subprocess.check_call([ |
| 612 'idevicefs', | 638 'idevicefs', |
| 613 '--udid', self.udid, | 639 '--udid', self.udid, |
| 614 'pull', | 640 'pull', |
| 615 '@%s/Documents' % self.cfbundleid, | 641 '@%s/Documents' % self.cfbundleid, |
| (...skipping 12 matching lines...) Expand all Loading... |
| 628 '--extract', | 654 '--extract', |
| 629 '--udid', self.udid, | 655 '--udid', self.udid, |
| 630 logs_dir, | 656 logs_dir, |
| 631 ]) | 657 ]) |
| 632 except subprocess.CalledProcessError: | 658 except subprocess.CalledProcessError: |
| 633 raise TestDataExtractionError() | 659 raise TestDataExtractionError() |
| 634 | 660 |
| 635 def tear_down(self): | 661 def tear_down(self): |
| 636 """Performs cleanup actions which must occur after every test launch.""" | 662 """Performs cleanup actions which must occur after every test launch.""" |
| 637 self.screenshot_desktop() | 663 self.screenshot_desktop() |
| 664 self.retrieve_derived_data() |
| 638 self.extract_test_data() | 665 self.extract_test_data() |
| 639 self.retrieve_crash_reports() | 666 self.retrieve_crash_reports() |
| 640 self.uninstall_apps() | 667 self.uninstall_apps() |
| 641 | 668 |
| 642 def get_launch_command(self, test_filter=None, invert=False): | 669 def get_launch_command(self, test_filter=None, invert=False): |
| 643 """Returns the command that can be used to launch the test app. | 670 """Returns the command that can be used to launch the test app. |
| 644 | 671 |
| 645 Args: | 672 Args: |
| 646 test_filter: List of test cases to filter. | 673 test_filter: List of test cases to filter. |
| 647 invert: Whether to invert the filter or not. Inverted, the filter will | 674 invert: Whether to invert the filter or not. Inverted, the filter will |
| (...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 691 """ | 718 """ |
| 692 env = super(DeviceTestRunner, self).get_launch_env() | 719 env = super(DeviceTestRunner, self).get_launch_env() |
| 693 if self.xctest_path: | 720 if self.xctest_path: |
| 694 env['NSUnbufferedIO'] = 'YES' | 721 env['NSUnbufferedIO'] = 'YES' |
| 695 # e.g. ios_web_shell_egtests | 722 # e.g. ios_web_shell_egtests |
| 696 env['APP_TARGET_NAME'] = os.path.splitext( | 723 env['APP_TARGET_NAME'] = os.path.splitext( |
| 697 os.path.basename(self.app_path))[0] | 724 os.path.basename(self.app_path))[0] |
| 698 # e.g. ios_web_shell_egtests_module | 725 # e.g. ios_web_shell_egtests_module |
| 699 env['TEST_TARGET_NAME'] = env['APP_TARGET_NAME'] + '_module' | 726 env['TEST_TARGET_NAME'] = env['APP_TARGET_NAME'] + '_module' |
| 700 return env | 727 return env |
| OLD | NEW |