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
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)
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\\"')])
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'])
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\\"'])
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)
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')