Esempio n. 1
0
def main() -> None:
    """Main function."""
    parser = argparse.ArgumentParser(allow_abbrev=False)
    parser.add_argument('--runfiles_env',
                        action='append',
                        type=_env_var,
                        default=[])
    parser.add_argument('--wrapper', type=pathlib.PurePosixPath, required=True)
    parser.add_argument('--mode', choices=('direct', 'wrap'), required=True)
    parser.add_argument('--rule-tag', action='append', default=[])
    parser.add_argument('--load-directory',
                        action='append',
                        type=pathlib.PurePosixPath,
                        default=[])
    parser.add_argument('--load-file',
                        action='append',
                        type=pathlib.PurePosixPath,
                        default=[])
    parser.add_argument('--data-file',
                        action='append',
                        type=pathlib.PurePosixPath,
                        default=[])
    parser.add_argument('--interactive', action='store_true', default=False)
    parser.add_argument('--input-arg', action='append', type=int, default=[])
    parser.add_argument('--output-arg', action='append', type=int, default=[])
    parser.add_argument('argv', nargs='+')
    opts = parser.parse_args()
    orig_env = dict(os.environ)
    # We don’t want the Python launcher to change the current working directory,
    # otherwise relative filenames will be all messed up.  See
    # https://github.com/bazelbuild/bazel/issues/7190.
    orig_env.pop('RUN_UNDER_RUNFILES', None)
    run_files = runfiles.Runfiles(dict(opts.runfiles_env))
    emacs = run_files.resolve(opts.wrapper)
    args = [str(emacs)]  # List[str]
    with manifest.add(opts.mode, args) as manifest_file:
        args.append('--quick')
        if not opts.interactive:
            args.append('--batch')
        load.add_path(run_files, args, opts.load_directory)
        for file in opts.load_file:
            abs_name = run_files.resolve(file)
            args.append('--load=' + str(abs_name))
        if manifest_file:
            runfiles_dir = _runfiles_dir(orig_env)
            input_files = _arg_files(opts.argv, runfiles_dir, opts.input_arg)
            output_files = _arg_files(opts.argv, runfiles_dir, opts.output_arg)
            manifest.write(opts, input_files, output_files, manifest_file)
        args.extend(opts.argv[1:])
        env = dict(orig_env)
        env.update(run_files.environment())
        try:
            subprocess.run(args, env=env, check=True)
        except subprocess.CalledProcessError as ex:
            if 0 < ex.returncode < 0x100:
                # Don’t print a stacktrace if Emacs exited with a non-zero exit
                # code.
                sys.exit(ex.returncode)
            raise
Esempio n. 2
0
 def test_version(self) -> None:
     """Tests that emacs --version works."""
     run_files = runfiles.Runfiles()
     emacs = run_files.resolve(
         pathlib.PurePosixPath('gnu_emacs_28.1/emacs' +
                               ('.exe' if os.name == 'nt' else '')))
     env = dict(os.environ)
     env.update(run_files.environment())
     process = subprocess.run([str(emacs), '--version'],
                              env=env,
                              check=False)
     self.assertEqual(process.returncode, 0)
Esempio n. 3
0
 def test_directory(self) -> None:
     """Unit test for directory-based runfiles."""
     args = ['--foo']
     with tempfile.TemporaryDirectory() as directory:
         load.add_path(runfiles.Runfiles({'RUNFILES_DIR': directory}), args,
                       [pathlib.PurePosixPath('foo'),
                        pathlib.PurePosixPath('bar \t\n\r\f äα𝐴🐈\'\0\\"')])
     base = pathlib.Path(directory)
     self.assertListEqual(
         args,
         ['--foo',
          '--directory=' + str(base / 'foo'),
          '--directory=' + str(base / 'bar \t\n\r\f äα𝐴🐈\'\0\\"')])
Esempio n. 4
0
 def test_run(self) -> None:
     """Runs the test binary and checks its output."""
     run_files = runfiles.Runfiles()
     binary = pathlib.PurePosixPath('phst_rules_elisp/examples/bin')
     if platform.system() == 'Windows':
         binary = binary.with_suffix('.exe')
     binary = run_files.resolve(binary)
     # Be sure to pass environment variables to find runfiles.  We also set
     # GCOV_PREFIX (see
     # https://gcc.gnu.org/onlinedocs/gcc/Cross-profiling.html) and
     # LLVM_PROFILE_FILE (see
     # https://clang.llvm.org/docs/SourceBasedCodeCoverage.html) to a
     # directory/file that’s hopefully writable, to avoid logspam when
     # running with “bazel coverage”.
     env = dict(run_files.environment(),
                GCOV_PREFIX=str(self._tempdir),
                LLVM_PROFILE_FILE=str(self._tempdir / 'bazel.%p.profraw'))
     for var in ('EMACS', 'PATH', 'SYSTEMROOT'):
         value = os.getenv(var)
         if value:
             env[var] = value
     # You can run the programs produced by elisp_binary rules like any other
     # binary.
     try:
         result = subprocess.run(
             [binary, 'human'], check=True,
             stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
             encoding='utf-8', env=env,
             # The working directory doesn’t matter.  Binaries still find
             # their runfiles.
             cwd='/')
     except subprocess.CalledProcessError as ex:
         print('Output of failing process:')
         print(ex.output, flush=True)
         raise
     lines = result.stdout.rstrip('\n').splitlines()
     # This message can happen depending on the mtime of files in the Bazel
     # sandbox.  It shouldn’t influence the test outcome.
     irrelevant = re.compile(
         r'^Source file .+ newer than byte-compiled file; using older file$')
     # We filter out some irrelevant messages that can cause spurious
     # failures.
     lines = [line for line in lines if not irrelevant.match(line)]
     self.assertListEqual(lines,
                          ['hi from bin, ("human")',
                           'hi from lib-2',
                           'hi from lib-4',
                           'hi from lib-1',
                           'hi from data dependency'])
Esempio n. 5
0
 def test_manifest(self) -> None:
     """Unit test for manifest-based runfiles."""
     runfiles_dir = pathlib.Path(
         r'C:\Runfiles' if os.name == 'nt' else '/runfiles')
     runfiles_elc = runfiles_dir / 'runfiles.elc'
     args = ['--foo']
     with tempfile.TemporaryDirectory() as directory:
         manifest = pathlib.Path(directory) / 'manifest'
         # Runfiles manifests contain POSIX-style filenames even on Windows.
         manifest.write_text('phst_rules_elisp/elisp/runfiles/runfiles.elc '
                             + runfiles_elc.as_posix() + '\n',
                             encoding='ascii')
         load.add_path(
             runfiles.Runfiles({'RUNFILES_MANIFEST_FILE': str(manifest)}),
             args,
             [pathlib.PurePosixPath('foo'),
              pathlib.PurePosixPath('bar \t\n\r\f äα𝐴🐈\'\0\\"')])
     self.assertListEqual(
         args,
         ['--foo',
          '--load=' + str(runfiles_elc),
          '--funcall=elisp/runfiles/install-handler',
          '--directory=/bazel-runfile:foo',
          '--directory=/bazel-runfile:bar \t\n\r\f äα𝐴🐈\'\0\\"'])
Esempio n. 6
0
def main() -> None:
    """Main function."""
    parser = argparse.ArgumentParser(allow_abbrev=False)
    parser.add_argument('--runfiles_env', action='append', type=_env_var,
                        default=[])
    parser.add_argument('--wrapper', type=pathlib.PurePosixPath, required=True)
    parser.add_argument('--mode', choices=('direct', 'wrap'), required=True)
    parser.add_argument('--rule-tag', action='append', default=[])
    parser.add_argument('--load-directory', action='append',
                        type=pathlib.PurePosixPath, default=[])
    parser.add_argument('--load-file', action='append',
                        type=pathlib.PurePosixPath, default=[])
    parser.add_argument('--data-file', action='append',
                        type=pathlib.PurePosixPath, default=[])
    parser.add_argument('--skip-test', action='append', default=[])
    parser.add_argument('--skip-tag', action='append', default=[])
    parser.add_argument('--module-assertions', action='store_true',
                        default=False)
    parser.add_argument('argv', nargs='+')
    opts = parser.parse_args()
    # Be a bit more verbose for tests, since Bazel will only show output on
    # explicit request.
    logging.basicConfig(level=logging.INFO,
                        format='%(asctime)s %(levelname)s %(name)s %(message)s')
    orig_env = dict(os.environ)
    # We don’t want the Python launcher to change the current working directory,
    # otherwise relative filenames will be all messed up.  See
    # https://github.com/bazelbuild/bazel/issues/7190.
    orig_env.pop('RUN_UNDER_RUNFILES', None)
    run_files = runfiles.Runfiles(dict(opts.runfiles_env))
    emacs = run_files.resolve(opts.wrapper)
    args = [str(emacs)]  # List[str]
    with manifest.add(opts.mode, args) as manifest_file:
        args += ['--quick', '--batch']
        if opts.module_assertions:
            args.append('--module-assertions')
        load.add_path(run_files, args, opts.load_directory)
        runner = run_files.resolve(
            pathlib.PurePosixPath('phst_rules_elisp/elisp/ert/runner.elc'))
        args.append('--load=' + str(runner))
        # Note that using equals signs for --test-source, --skip-test, and
        # --skip-tag doesn’t work.
        for file in opts.load_file:
            abs_name = run_files.resolve(file)
            args += ['--test-source', '/:' + _quote(str(abs_name))]
        for test in opts.skip_test:
            args += ['--skip-test', _quote(test)]
        for tag in opts.skip_tag:
            args += ['--skip-tag', _quote(tag)]
        args.append('--funcall=elisp/ert/run-batch-and-exit')
        if manifest_file:
            inputs = []  # type: List[pathlib.Path]
            outputs = []  # type: List[pathlib.Path]
            report_file = orig_env.get('XML_OUTPUT_FILE')
            if report_file:
                outputs.append(pathlib.Path(report_file))
            if orig_env.get('COVERAGE') == '1':
                coverage_manifest = orig_env.get('COVERAGE_MANIFEST')
                if coverage_manifest:
                    coverage_manifest = pathlib.Path(coverage_manifest)
                    _fix_coverage_manifest(coverage_manifest, run_files)
                    inputs.append(coverage_manifest)
                coverage_dir = orig_env.get('COVERAGE_DIR')
                if coverage_dir:
                    outputs.append(
                        pathlib.Path(coverage_dir) / 'emacs-lisp.dat')
            manifest.write(opts, inputs, outputs, manifest_file)
        args.extend(opts.argv[1:])
        env = dict(orig_env)
        env.update(run_files.environment())
        timeout_secs = None
        kwargs = {}
        if _WINDOWS:
            # On Windows, the Bazel test runner doesn’t gracefully kill the test
            # process, see https://github.com/bazelbuild/bazel/issues/12684.  We
            # work around this by creating a new process group and sending
            # CTRL + BREAK slightly before Bazel kills us.
            timeout_str = orig_env.get('TEST_TIMEOUT') or None
            if timeout_str:
                # Lower the timeout to account for infrastructure overhead.
                timeout_secs = int(timeout_str) - 2
            flags = subprocess.CREATE_NEW_PROCESS_GROUP  # pylint: disable=line-too-long  # pytype: disable=module-attr
            kwargs['creationflags'] = flags
        # We can’t use subprocess.run on Windows because it terminates the
        # subprocess using TerminateProcess on timeout, giving it no chance to
        # clean up after itself.
        with subprocess.Popen(args, env=env, **kwargs) as process:
            try:
                process.communicate(timeout=timeout_secs)
            except subprocess.TimeoutExpired:
                # Since we pass a None timeout on Unix systems, we should get
                # here only on Windows.
                assert _WINDOWS
                _logger.warning('test timed out, sending CTRL + BREAK')
                signum = signal.CTRL_BREAK_EVENT  # pylint: disable=no-member,line-too-long  # pytype: disable=module-attr
                process.send_signal(signum)
                _logger.info('waiting for Bazel to kill this process')
                # We want timeouts to be reflected as actual timeout results in
                # Bazel, so we force a Bazel-level timeout by sleeping for a
                # long time.
                time.sleep(20)
                # If Bazel hasn’t killed us, exit anyway.
                _logger.warning('Bazel failed to kill this process')
                sys.exit(0xFF)
            returncode = process.wait()
        if returncode:
            # Don’t print a stacktrace if Emacs exited with a non-zero exit
            # code.
            sys.exit(returncode)
Esempio n. 7
0
def main() -> None:
    """Main function."""
    if isinstance(sys.stdout, io.TextIOWrapper):  # typical case
        sys.stdout.reconfigure(encoding='utf-8',
                               errors='backslashreplace',
                               line_buffering=True)
    print('Args:', sys.argv)
    print('Environment:', os.environ)
    parser = argparse.ArgumentParser(allow_abbrev=False)
    parser.add_argument('--manifest', type=pathlib.Path, required=True)
    parser.add_argument('rest', nargs='+')
    args = parser.parse_args()
    run_files = runfiles.Runfiles()
    output_file = pathlib.PurePath(r'C:\Temp\output.dat' if os.name ==
                                   'nt' else '/tmp/output.dat')

    class Test(unittest.TestCase):
        """Unit tests for the command line and manifest."""

        maxDiff = 5000

        def test_args(self) -> None:
            """Test that the Emacs command line is as expected."""
            got = args.rest
            want = ['--quick', '--batch']
            # The load path setup depends on whether we use manifest-based or
            # directory-based runfiles.
            try:
                directory = run_files.resolve(
                    pathlib.PurePosixPath('phst_rules_elisp'))
            except FileNotFoundError:
                # Manifest-based runfiles.
                want += [
                    '--load=' + str(
                        run_files.resolve(
                            pathlib.PurePosixPath(
                                'phst_rules_elisp/elisp/runfiles/runfiles.elc')
                        )),
                    '--funcall=elisp/runfiles/install-handler',
                    '--directory=/bazel-runfile:phst_rules_elisp',
                ]
            else:
                # Directory-based runfiles.
                want.append('--directory=' + str(directory))
            want += [
                '--option',
                str(
                    run_files.resolve(
                        pathlib.PurePosixPath(
                            'phst_rules_elisp/elisp/binary.cc'))),
                ' \t\n\r\f äα𝐴🐈\'\\"',
                '/:' + str(output_file),
            ]
            self.assertListEqual(got, want)

        def test_manifest(self) -> None:
            """Test the manifest."""
            got = json.loads(args.manifest.read_text(encoding='utf-8'))
            want = {
                'root':
                'RUNFILES_ROOT',
                'tags': ['local', 'mytag'],
                'loadPath': ['phst_rules_elisp'],
                'inputFiles': [
                    'phst_rules_elisp/elisp/binary.cc',
                    'phst_rules_elisp/elisp/binary.h'
                ],
                'outputFiles': [str(output_file)],
            }  # type: Dict[str, Any]
            for var in (got, want):
                files = var.get('inputFiles', [])  # type: List[str]
                for i, file in enumerate(files):
                    file = pathlib.PurePosixPath(file)
                    if not file.is_absolute():
                        files[i] = str(run_files.resolve(file))
            self.assertDictEqual(got, want)

    tests = unittest.defaultTestLoader.loadTestsFromTestCase(Test)
    if not unittest.TextTestRunner().run(tests).wasSuccessful():
        raise ValueError('incorrect arguments')