|
1 |
| -# |
2 | 1 | # This Source Code Form is subject to the terms of the Mozilla Public
|
3 | 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this
|
4 | 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8 | 7 | used to isolate and minimize differential behaviour test cases.
|
9 | 8 |
|
10 | 9 | Example:
|
11 |
| - python -m lithium diff_test -a "--fuzzing-safe" \ |
12 |
| - -b "--fuzzing-safe --wasm-always-baseline" <binary> <testcase> |
13 |
| -
|
14 |
| -Example with autobisectjs, split into separate lines here for readability: |
15 |
| - python -u -m funfuzz.autobisectjs.autobisectjs \ |
16 |
| - -b "--enable-debug --enable-more-deterministic" -p testcase.js \ |
17 |
| - -i diff_test -a "--fuzzing-safe --no-threads --ion-eager" \ |
18 |
| - -b "--fuzzing-safe --no-threads --ion-eager --no-wasm-baseline" |
| 10 | + python -m lithium diff_test \ |
| 11 | + -a "--fuzzing-safe" \ |
| 12 | + -b "--fuzzing-safe --wasm-always-baseline" \ |
| 13 | + <binary> <testcase> |
19 | 14 | """
|
20 |
| - |
21 |
| -# This file came from nbp's GitHub PR #2 for adding new Lithium reduction strategies. |
22 |
| -# https://github.com/MozillaSecurity/lithium/pull/2 |
23 |
| - |
| 15 | +import argparse |
24 | 16 | import filecmp
|
25 | 17 | import logging
|
26 |
| -from typing import List |
| 18 | +import sys |
| 19 | +from typing import List, Optional, Union |
27 | 20 |
|
28 |
| -from . import timed_run |
| 21 | +from .timed_run import BaseParser, ExitStatus, timed_run |
29 | 22 |
|
| 23 | +LOG = logging.getLogger(__name__) |
30 | 24 |
|
31 |
| -def interesting(cli_args: List[str], temp_prefix: str) -> bool: |
32 |
| - """Interesting if the binary shows a difference in output when different command |
33 |
| - line arguments are passed in. |
| 25 | + |
| 26 | +def parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace: |
| 27 | + """Parse args |
34 | 28 |
|
35 | 29 | Args:
|
36 |
| - cli_args: List of input arguments. |
37 |
| - temp_prefix: Temporary directory prefix, e.g. tmp1/1 or tmp4/1 |
| 30 | + argv: List of input arguments. |
38 | 31 |
|
39 | 32 | Returns:
|
40 |
| - True if a difference in output appears, False otherwise. |
| 33 | + Parsed arguments |
41 | 34 | """
|
42 |
| - parser = timed_run.ArgumentParser( |
| 35 | + parser = BaseParser( |
43 | 36 | prog="diff_test",
|
44 |
| - usage="python -m lithium %(prog)s [options] binary testcase.ext", |
| 37 | + usage="python -m lithium.interestingness.diff " |
| 38 | + "-a '--fuzzing-safe' -b='' binary testcase.js", |
45 | 39 | )
|
46 | 40 | parser.add_argument(
|
47 | 41 | "-a",
|
48 |
| - "--a-args", |
49 | 42 | dest="a_args",
|
50 | 43 | help="Set of extra arguments given to first run.",
|
| 44 | + required=True, |
51 | 45 | )
|
52 | 46 | parser.add_argument(
|
53 | 47 | "-b",
|
54 |
| - "--b-args", |
55 | 48 | dest="b_args",
|
56 | 49 | help="Set of extra arguments given to second run.",
|
| 50 | + required=True, |
57 | 51 | )
|
58 |
| - args = parser.parse_args(cli_args) |
59 | 52 |
|
60 |
| - a_runinfo = timed_run.timed_run( |
61 |
| - args.cmd_with_flags[:1] + args.a_args.split() + args.cmd_with_flags[1:], |
62 |
| - args.timeout, |
63 |
| - temp_prefix + "-a", |
64 |
| - ) |
65 |
| - b_runinfo = timed_run.timed_run( |
66 |
| - args.cmd_with_flags[:1] + args.b_args.split() + args.cmd_with_flags[1:], |
67 |
| - args.timeout, |
68 |
| - temp_prefix + "-b", |
69 |
| - ) |
70 |
| - log = logging.getLogger(__name__) |
71 |
| - time_str = ( |
72 |
| - f"(1st Run: {a_runinfo.elapsedtime:.3f} seconds)" |
73 |
| - f" (2nd Run: {b_runinfo.elapsedtime:.3f} seconds)" |
74 |
| - ) |
| 53 | + args = parser.parse_args(argv) |
| 54 | + if not args.cmd_with_flags: |
| 55 | + parser.error("Must specify command to evaluate.") |
75 | 56 |
|
76 |
| - if timed_run.TIMED_OUT not in (a_runinfo.sta, b_runinfo.sta): |
77 |
| - if a_runinfo.return_code != b_runinfo.return_code: |
78 |
| - log.info( |
79 |
| - "[Interesting] Different return code (%d, %d). %s", |
80 |
| - a_runinfo.return_code, |
81 |
| - b_runinfo.return_code, |
82 |
| - time_str, |
83 |
| - ) |
84 |
| - return True |
85 |
| - if not filecmp.cmp(a_runinfo.out, b_runinfo.out): |
86 |
| - log.info("[Interesting] Different output. %s", time_str) |
87 |
| - return True |
88 |
| - if not filecmp.cmp(a_runinfo.err, b_runinfo.err): |
89 |
| - log.info("[Interesting] Different error output. %s", time_str) |
| 57 | + return args |
| 58 | + |
| 59 | + |
| 60 | +def interesting( |
| 61 | + cli_args: Optional[List[str]] = None, |
| 62 | + temp_prefix: Optional[str] = None, |
| 63 | +) -> bool: |
| 64 | + """Check if there's a difference in output or return code with different args. |
| 65 | +
|
| 66 | + Args: |
| 67 | + cli_args: Input arguments. |
| 68 | + temp_prefix: Temporary directory prefix, e.g. tmp1/1. |
| 69 | +
|
| 70 | + Returns: |
| 71 | + True if a difference in output appears, False otherwise. |
| 72 | + """ |
| 73 | + args = parse_args(cli_args) |
| 74 | + |
| 75 | + binary = args.cmd_with_flags[:1] |
| 76 | + testcase = args.cmd_with_flags[1:] |
| 77 | + |
| 78 | + # Run with arguments set A |
| 79 | + command_a = binary + args.a_args.split() + testcase |
| 80 | + log_prefix_a = f"{temp_prefix}-a" if temp_prefix else None |
| 81 | + a_run = timed_run(command_a, args.timeout, log_prefix_a) |
| 82 | + if a_run.status == ExitStatus.TIMEOUT: |
| 83 | + LOG.warning("Command A timed out!") |
| 84 | + |
| 85 | + # Run with arguments set B |
| 86 | + command_b = binary + args.b_args.split() + testcase |
| 87 | + log_prefix_b = f"{temp_prefix}-b" if temp_prefix else None |
| 88 | + b_run = timed_run(command_b, args.timeout, log_prefix_b) |
| 89 | + if b_run.status == ExitStatus.TIMEOUT: |
| 90 | + LOG.warning("Command B timed out!") |
| 91 | + |
| 92 | + # Compare return codes |
| 93 | + a_ret = a_run.return_code |
| 94 | + b_ret = b_run.return_code |
| 95 | + if a_ret != b_ret: |
| 96 | + LOG.info(f"[Interesting] Different return codes: {a_ret} vs {b_ret}") |
| 97 | + return True |
| 98 | + |
| 99 | + # Compare outputs |
| 100 | + def cmp_out( |
| 101 | + a_data: Union[str, bytes], |
| 102 | + b_run: Union[str, bytes], |
| 103 | + is_file: bool = False, |
| 104 | + ) -> bool: |
| 105 | + if is_file: |
| 106 | + return not filecmp.cmp(a_data, b_run) |
| 107 | + return a_data != b_run |
| 108 | + |
| 109 | + if temp_prefix: |
| 110 | + if cmp_out(a_run.out, b_run.out, True) or cmp_out(a_run.err, b_run.err, True): |
| 111 | + LOG.info("[Interesting] Differences in output detected") |
90 | 112 | return True
|
91 | 113 | else:
|
92 |
| - log.info("[Uninteresting] At least one test timed out. %s", time_str) |
93 |
| - return False |
| 114 | + if cmp_out(a_run.out, b_run.out) or cmp_out(a_run.err, b_run.err): |
| 115 | + LOG.info("[Interesting] Differences in output detected") |
| 116 | + return True |
94 | 117 |
|
95 |
| - log.info("[Uninteresting] Identical behaviour. %s", time_str) |
| 118 | + LOG.info("[Uninteresting] No differences detected") |
96 | 119 | return False
|
| 120 | + |
| 121 | + |
| 122 | +if __name__ == "__main__": |
| 123 | + logging.basicConfig(format="%(message)s", level=logging.INFO) |
| 124 | + sys.exit(interesting()) |
0 commit comments