OLD | NEW |
1 # Copyright (c) 2012 Google Inc. All rights reserved. | 1 # Copyright (c) 2012 Google Inc. 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 """ | 5 """ |
6 TestGyp.py: a testing framework for GYP integration tests. | 6 TestGyp.py: a testing framework for GYP integration tests. |
7 """ | 7 """ |
8 | 8 |
9 import collections | 9 import collections |
10 from contextlib import contextmanager | 10 from contextlib import contextmanager |
11 import itertools | 11 import itertools |
12 import os | 12 import os |
13 import random | |
14 import re | 13 import re |
15 import shutil | 14 import shutil |
16 import stat | |
17 import subprocess | 15 import subprocess |
18 import sys | 16 import sys |
19 import tempfile | 17 import tempfile |
20 import time | |
21 | 18 |
22 import TestCmd | 19 import TestCmd |
23 import TestCommon | 20 import TestCommon |
24 from TestCommon import __all__ | 21 from TestCommon import __all__ |
25 | 22 |
26 __all__.extend([ | 23 __all__.extend([ |
27 'TestGyp', | 24 'TestGyp', |
28 ]) | 25 ]) |
29 | 26 |
30 | 27 |
(...skipping 373 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 class TestGypCustom(TestGypBase): | 401 class TestGypCustom(TestGypBase): |
405 """ | 402 """ |
406 Subclass for testing the GYP with custom generator | 403 Subclass for testing the GYP with custom generator |
407 """ | 404 """ |
408 | 405 |
409 def __init__(self, gyp=None, *args, **kw): | 406 def __init__(self, gyp=None, *args, **kw): |
410 self.format = kw.pop("format") | 407 self.format = kw.pop("format") |
411 super(TestGypCustom, self).__init__(*args, **kw) | 408 super(TestGypCustom, self).__init__(*args, **kw) |
412 | 409 |
413 | 410 |
414 class TestGypAndroid(TestGypBase): | |
415 """ | |
416 Subclass for testing the GYP Android makefile generator. Note that | |
417 build/envsetup.sh and lunch must have been run before running tests. | |
418 """ | |
419 format = 'android' | |
420 | |
421 # Note that we can't use mmm as the build tool because ... | |
422 # - it builds all targets, whereas we need to pass a target | |
423 # - it is a function, whereas the test runner assumes the build tool is a file | |
424 # Instead we use make and duplicate the logic from mmm. | |
425 build_tool_list = ['make'] | |
426 | |
427 # We use our custom target 'gyp_all_modules', as opposed to the 'all_modules' | |
428 # target used by mmm, to build only those targets which are part of the gyp | |
429 # target 'all'. | |
430 ALL = 'gyp_all_modules' | |
431 | |
432 def __init__(self, gyp=None, *args, **kw): | |
433 # Android requires build and test output to be inside its source tree. | |
434 # We use the following working directory for the test's source, but the | |
435 # test's build output still goes to $ANDROID_PRODUCT_OUT. | |
436 # Note that some tests explicitly set format='gypd' to invoke the gypd | |
437 # backend. This writes to the source tree, but there's no way around this. | |
438 kw['workdir'] = os.path.join('/tmp', 'gyptest', | |
439 kw.get('workdir', 'testworkarea')) | |
440 # We need to remove all gyp outputs from out/. Ths is because some tests | |
441 # don't have rules to regenerate output, so they will simply re-use stale | |
442 # output if present. Since the test working directory gets regenerated for | |
443 # each test run, this can confuse things. | |
444 # We don't have a list of build outputs because we don't know which | |
445 # dependent targets were built. Instead we delete all gyp-generated output. | |
446 # This may be excessive, but should be safe. | |
447 out_dir = os.environ['ANDROID_PRODUCT_OUT'] | |
448 obj_dir = os.path.join(out_dir, 'obj') | |
449 shutil.rmtree(os.path.join(obj_dir, 'GYP'), ignore_errors = True) | |
450 for x in ['EXECUTABLES', 'STATIC_LIBRARIES', 'SHARED_LIBRARIES']: | |
451 for d in os.listdir(os.path.join(obj_dir, x)): | |
452 if d.endswith('_gyp_intermediates'): | |
453 shutil.rmtree(os.path.join(obj_dir, x, d), ignore_errors = True) | |
454 for x in [os.path.join('obj', 'lib'), os.path.join('system', 'lib')]: | |
455 for d in os.listdir(os.path.join(out_dir, x)): | |
456 if d.endswith('_gyp.so'): | |
457 os.remove(os.path.join(out_dir, x, d)) | |
458 | |
459 super(TestGypAndroid, self).__init__(*args, **kw) | |
460 self._adb_path = os.path.join(os.environ['ANDROID_HOST_OUT'], 'bin', 'adb') | |
461 self._device_serial = None | |
462 adb_devices_out = self._call_adb(['devices']) | |
463 devices = [l.split()[0] for l in adb_devices_out.splitlines()[1:-1] | |
464 if l.split()[1] == 'device'] | |
465 if len(devices) == 0: | |
466 self._device_serial = None | |
467 else: | |
468 if len(devices) > 1: | |
469 self._device_serial = random.choice(devices) | |
470 else: | |
471 self._device_serial = devices[0] | |
472 self._call_adb(['root']) | |
473 self._to_install = set() | |
474 | |
475 def target_name(self, target): | |
476 if target == self.ALL: | |
477 return self.ALL | |
478 # The default target is 'droid'. However, we want to use our special target | |
479 # to build only the gyp target 'all'. | |
480 if target in (None, self.DEFAULT): | |
481 return self.ALL | |
482 return target | |
483 | |
484 _INSTALLABLE_PREFIX = 'Install: ' | |
485 | |
486 def build(self, gyp_file, target=None, **kw): | |
487 """ | |
488 Runs a build using the Android makefiles generated from the specified | |
489 gyp_file. This logic is taken from Android's mmm. | |
490 """ | |
491 arguments = kw.get('arguments', [])[:] | |
492 arguments.append(self.target_name(target)) | |
493 arguments.append('-C') | |
494 arguments.append(os.environ['ANDROID_BUILD_TOP']) | |
495 kw['arguments'] = arguments | |
496 chdir = kw.get('chdir', '') | |
497 makefile = os.path.join(self.workdir, chdir, 'GypAndroid.mk') | |
498 os.environ['ONE_SHOT_MAKEFILE'] = makefile | |
499 result = self.run(program=self.build_tool, **kw) | |
500 for l in self.stdout().splitlines(): | |
501 if l.startswith(TestGypAndroid._INSTALLABLE_PREFIX): | |
502 self._to_install.add(os.path.abspath(os.path.join( | |
503 os.environ['ANDROID_BUILD_TOP'], | |
504 l[len(TestGypAndroid._INSTALLABLE_PREFIX):]))) | |
505 del os.environ['ONE_SHOT_MAKEFILE'] | |
506 return result | |
507 | |
508 def android_module(self, group, name, subdir): | |
509 if subdir: | |
510 name = '%s_%s' % (subdir, name) | |
511 if group == 'SHARED_LIBRARIES': | |
512 name = 'lib_%s' % name | |
513 return '%s_gyp' % name | |
514 | |
515 def intermediates_dir(self, group, module_name): | |
516 return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', group, | |
517 '%s_intermediates' % module_name) | |
518 | |
519 def built_file_path(self, name, type=None, **kw): | |
520 """ | |
521 Returns a path to the specified file name, of the specified type, | |
522 as built by Android. Note that we don't support the configuration | |
523 parameter. | |
524 """ | |
525 # Built files are in $ANDROID_PRODUCT_OUT. This requires copying logic from | |
526 # the Android build system. | |
527 if type == None or type == self.EXECUTABLE: | |
528 return os.path.join(os.environ['ANDROID_PRODUCT_OUT'], 'obj', 'GYP', | |
529 'shared_intermediates', name) | |
530 subdir = kw.get('subdir') | |
531 if type == self.STATIC_LIB: | |
532 group = 'STATIC_LIBRARIES' | |
533 module_name = self.android_module(group, name, subdir) | |
534 return os.path.join(self.intermediates_dir(group, module_name), | |
535 '%s.a' % module_name) | |
536 if type == self.SHARED_LIB: | |
537 group = 'SHARED_LIBRARIES' | |
538 module_name = self.android_module(group, name, subdir) | |
539 return os.path.join(self.intermediates_dir(group, module_name), 'LINKED', | |
540 '%s.so' % module_name) | |
541 assert False, 'Unhandled type' | |
542 | |
543 def _adb_failure(self, command, msg, stdout, stderr): | |
544 """ Reports a failed adb command and fails the containing test. | |
545 | |
546 Args: | |
547 command: The adb command that failed. | |
548 msg: The error description. | |
549 stdout: The standard output. | |
550 stderr: The standard error. | |
551 """ | |
552 print '%s failed%s' % (' '.join(command), ': %s' % msg if msg else '') | |
553 print self.banner('STDOUT ') | |
554 stdout.seek(0) | |
555 print stdout.read() | |
556 print self.banner('STDERR ') | |
557 stderr.seek(0) | |
558 print stderr.read() | |
559 self.fail_test() | |
560 | |
561 def _call_adb(self, command, timeout=15, retry=3): | |
562 """ Calls the provided adb command. | |
563 | |
564 If the command fails, the test fails. | |
565 | |
566 Args: | |
567 command: The adb command to call. | |
568 Returns: | |
569 The command's output. | |
570 """ | |
571 with tempfile.TemporaryFile(bufsize=0) as adb_out: | |
572 with tempfile.TemporaryFile(bufsize=0) as adb_err: | |
573 adb_command = [self._adb_path] | |
574 if self._device_serial: | |
575 adb_command += ['-s', self._device_serial] | |
576 is_shell = (command[0] == 'shell') | |
577 if is_shell: | |
578 command = [command[0], '%s; echo "\n$?";' % ' '.join(command[1:])] | |
579 adb_command += command | |
580 | |
581 for attempt in xrange(1, retry + 1): | |
582 adb_out.seek(0) | |
583 adb_out.truncate(0) | |
584 adb_err.seek(0) | |
585 adb_err.truncate(0) | |
586 proc = subprocess.Popen(adb_command, stdout=adb_out, stderr=adb_err) | |
587 deadline = time.time() + timeout | |
588 timed_out = False | |
589 while proc.poll() is None and not timed_out: | |
590 time.sleep(1) | |
591 timed_out = time.time() > deadline | |
592 if timed_out: | |
593 print 'Timeout for command %s (attempt %d of %s)' % ( | |
594 adb_command, attempt, retry) | |
595 try: | |
596 proc.kill() | |
597 except: | |
598 pass | |
599 else: | |
600 break | |
601 | |
602 if proc.returncode != 0: # returncode is None in the case of a timeout. | |
603 self._adb_failure( | |
604 adb_command, 'retcode=%s' % proc.returncode, adb_out, adb_err) | |
605 return | |
606 | |
607 adb_out.seek(0) | |
608 output = adb_out.read() | |
609 if is_shell: | |
610 output = output.splitlines(True) | |
611 try: | |
612 output[-2] = output[-2].rstrip('\r\n') | |
613 output, rc = (''.join(output[:-1]), int(output[-1])) | |
614 except ValueError: | |
615 self._adb_failure(adb_command, 'unexpected output format', | |
616 adb_out, adb_err) | |
617 if rc != 0: | |
618 self._adb_failure(adb_command, 'exited with %d' % rc, adb_out, | |
619 adb_err) | |
620 return output | |
621 | |
622 def run_built_executable(self, name, *args, **kw): | |
623 """ | |
624 Runs an executable program built from a gyp-generated configuration. | |
625 """ | |
626 match = kw.pop('match', self.match) | |
627 | |
628 executable_file = self.built_file_path(name, type=self.EXECUTABLE, **kw) | |
629 if executable_file not in self._to_install: | |
630 self.fail_test() | |
631 | |
632 if not self._device_serial: | |
633 self.skip_test(message='No devices attached.\n') | |
634 | |
635 storage = self._call_adb(['shell', 'echo', '$ANDROID_DATA']).strip() | |
636 if not len(storage): | |
637 self.fail_test() | |
638 | |
639 installed = set() | |
640 try: | |
641 for i in self._to_install: | |
642 a = os.path.abspath( | |
643 os.path.join(os.environ['ANDROID_BUILD_TOP'], i)) | |
644 dest = '%s/%s' % (storage, os.path.basename(a)) | |
645 self._call_adb(['push', os.path.abspath(a), dest]) | |
646 installed.add(dest) | |
647 if i == executable_file: | |
648 device_executable = dest | |
649 self._call_adb(['shell', 'chmod', '755', device_executable]) | |
650 | |
651 out = self._call_adb( | |
652 ['shell', 'LD_LIBRARY_PATH=$LD_LIBRARY_PATH:%s' % storage, | |
653 device_executable], | |
654 timeout=60, | |
655 retry=1) | |
656 out = out.replace('\r\n', '\n') | |
657 self._complete(out, kw.pop('stdout', None), None, None, None, match) | |
658 finally: | |
659 if len(installed): | |
660 self._call_adb(['shell', 'rm'] + list(installed)) | |
661 | |
662 def match_single_line(self, lines = None, expected_line = None): | |
663 """ | |
664 Checks that specified line appears in the text. | |
665 """ | |
666 for line in lines.split('\n'): | |
667 if line == expected_line: | |
668 return 1 | |
669 return | |
670 | |
671 def up_to_date(self, gyp_file, target=None, **kw): | |
672 """ | |
673 Verifies that a build of the specified target is up to date. | |
674 """ | |
675 kw['stdout'] = ("make: Nothing to be done for `%s'." % | |
676 self.target_name(target)) | |
677 | |
678 # We need to supply a custom matcher, since we don't want to depend on the | |
679 # exact stdout string. | |
680 kw['match'] = self.match_single_line | |
681 return self.build(gyp_file, target, **kw) | |
682 | |
683 | |
684 class TestGypCMake(TestGypBase): | 411 class TestGypCMake(TestGypBase): |
685 """ | 412 """ |
686 Subclass for testing the GYP CMake generator, using cmake's ninja backend. | 413 Subclass for testing the GYP CMake generator, using cmake's ninja backend. |
687 """ | 414 """ |
688 format = 'cmake' | 415 format = 'cmake' |
689 build_tool_list = ['cmake'] | 416 build_tool_list = ['cmake'] |
690 ALL = 'all' | 417 ALL = 'all' |
691 | 418 |
692 def cmake_build(self, gyp_file, target=None, **kw): | 419 def cmake_build(self, gyp_file, target=None, **kw): |
693 arguments = kw.get('arguments', [])[:] | 420 arguments = kw.get('arguments', [])[:] |
(...skipping 698 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1392 """ | 1119 """ |
1393 configuration = self.configuration_dirname() | 1120 configuration = self.configuration_dirname() |
1394 os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration) | 1121 os.environ['DYLD_LIBRARY_PATH'] = os.path.join('out', configuration) |
1395 # Enclosing the name in a list avoids prepending the original dir. | 1122 # Enclosing the name in a list avoids prepending the original dir. |
1396 program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)] | 1123 program = [self.built_file_path(name, type=self.EXECUTABLE, **kw)] |
1397 return self.run(program=program, *args, **kw) | 1124 return self.run(program=program, *args, **kw) |
1398 | 1125 |
1399 | 1126 |
1400 format_class_list = [ | 1127 format_class_list = [ |
1401 TestGypGypd, | 1128 TestGypGypd, |
1402 TestGypAndroid, | |
1403 TestGypCMake, | 1129 TestGypCMake, |
1404 TestGypMake, | 1130 TestGypMake, |
1405 TestGypMSVS, | 1131 TestGypMSVS, |
1406 TestGypMSVSNinja, | 1132 TestGypMSVSNinja, |
1407 TestGypNinja, | 1133 TestGypNinja, |
1408 TestGypXcode, | 1134 TestGypXcode, |
1409 TestGypXcodeNinja, | 1135 TestGypXcodeNinja, |
1410 ] | 1136 ] |
1411 | 1137 |
1412 def TestGyp(*args, **kw): | 1138 def TestGyp(*args, **kw): |
1413 """ | 1139 """ |
1414 Returns an appropriate TestGyp* instance for a specified GYP format. | 1140 Returns an appropriate TestGyp* instance for a specified GYP format. |
1415 """ | 1141 """ |
1416 format = kw.pop('format', os.environ.get('TESTGYP_FORMAT')) | 1142 format = kw.pop('format', os.environ.get('TESTGYP_FORMAT')) |
1417 for format_class in format_class_list: | 1143 for format_class in format_class_list: |
1418 if format == format_class.format: | 1144 if format == format_class.format: |
1419 return format_class(*args, **kw) | 1145 return format_class(*args, **kw) |
1420 raise Exception, "unknown format %r" % format | 1146 raise Exception, "unknown format %r" % format |
OLD | NEW |