From e58b5dcda599ab5d85055dbf7c8924241c9d82f7 Mon Sep 17 00:00:00 2001 From: longvo920 Date: Sun, 27 Sep 2026 15:14:53 +0700 Subject: [PATCH] feat: improve terminal-only diagnostics --- CHANGELOG.md | 8 ++ README.md | 4 +- compare_tool/__init__.py | 2 +- compare_tool/main.py | 170 ++++++++++++++++++++++++++++----------- docs/architecture.md | 2 +- docs/usage.md | 10 ++- docs/vi/architecture.md | 2 +- docs/vi/usage.md | 10 ++- tests/test_cli_modes.py | 110 +++++++++++++++++++++---- 9 files changed, 247 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a444c21..2bfe424 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project are documented here. Versions follow [semantic versioning](https://semver.org/). +## [Unreleased] + +## [1.17.0] — 2026-09-27 + +### Changed + +- Improve terminal-only comparisons with built-in diagnostics and concise per-file summaries. + ## [1.16.0] — 2026-09-15 ### Added diff --git a/README.md b/README.md index c1e65ad..7f8f89d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Print only a terminal summary: python -m compare_tool baseline current --no-report ``` -The terminal mode shows a per-model Overview, every file and its verdict, AUTOSAR/A2L changes, and consistency warnings. It does not print source diffs or create HTML. +The terminal mode shows a per-model Overview, AUTOSAR/A2L changes, consistency warnings, effective compare settings, hunk-classification totals and one summary for each non-identical file. It does not print individual hunks, source diffs or create HTML. Open the desktop viewer: @@ -62,7 +62,7 @@ Folders and ZIP archives are accepted as inputs. |---|---| | Desktop viewer | Interactive side-by-side review | | HTML report | Sharing or publishing as a CI artifact | -| `--no-report` | Fast terminal review without generated files | +| `--no-report` | Detailed terminal review and diagnostics without generated files | | `--json` | Structured pipeline processing | | `--sarif` | Code-scanning annotations | diff --git a/compare_tool/__init__.py b/compare_tool/__init__.py index da8606e..410f351 100644 --- a/compare_tool/__init__.py +++ b/compare_tool/__init__.py @@ -1,3 +1,3 @@ """CodeGen Compare Tool - AUTOSAR MATLAB codegen diff with noise filtering.""" -__version__ = "1.16.0" +__version__ = "1.17.0" diff --git a/compare_tool/main.py b/compare_tool/main.py index 50b2163..a973bc5 100644 --- a/compare_tool/main.py +++ b/compare_tool/main.py @@ -10,13 +10,15 @@ """ import argparse +import platform import shutil import sys import tempfile +from collections import Counter from pathlib import Path from . import __version__, review, serialize, theme, userrules, zipsource -from .diff_engine import RULES +from .diff_engine import RULES, ruleset_for from .report import (build_arxml_report, build_report, consistency_advisories, model_overview) from .view_model import SWC_DISPLAY, iface_kind, swc_item @@ -103,36 +105,6 @@ def run_compare(old_root, new_root, out, arxml_only=False, exclude=(), return results, counts -def _terminal_tree_lines(results): - """ASCII folder tree with a verdict for every scanned path.""" - root = {} - for rel in sorted(results): - parts = rel.replace('\\', '/').split('/') - node = root - for part in parts[:-1]: - node = node.setdefault(part + '/', {}) - node[parts[-1]] = results[rel]['status'] - lines = ['Folder tree:'] - - def walk(node, prefix): - entries = sorted(node, key=lambda name: (not name.endswith('/'), name)) - for index, name in enumerate(entries): - last = index == len(entries) - 1 - branch = '`-- ' if last else '|-- ' - value = node[name] - if isinstance(value, dict): - lines.append(prefix + branch + name) - walk(value, prefix + (' ' if last else '| ')) - else: - label = 'modified' if value == 'real-change' else value - lines.append('{}{}{} [{}]'.format(prefix, branch, name, label)) - - walk(root, '') - if not root: - lines.append(' (no files matched)') - return lines - - def _terminal_overview_lines(results): """Aligned per-model Overview using the report's renderer-neutral rows.""" rows = model_overview(results) @@ -170,12 +142,12 @@ def autosar_text(parts): return lines -def summary_lines(results, counts, tree=False): +def summary_lines(results, counts, terminal=False): """Scan summary as plain-text lines the CLI prints: counts, uncompared paths, modified files and the AUTOSAR/A2L semantic rollups. - ``tree=True`` replaces modified-file hunk counts with every scanned path - and its verdict. Error and consistency warnings are shared by both modes. + ``terminal=True`` adds the per-model Overview and a semantic-section + heading for ``--no-report``. Per-file diagnostics are rendered separately. """ lines = [] lines.append('Summary: {real-change} modified, {comment-only} comment-only, ' @@ -196,14 +168,12 @@ def summary_lines(results, counts, tree=False): if r['status'] == 'error': for note in r['notes']: lines.append(' !! {} -- {}'.format(rel, note)) - if tree: - lines.append('') + if terminal: overview = _terminal_overview_lines(results) if overview: - lines.extend(overview) lines.append('') - lines.extend(_terminal_tree_lines(results)) - modified_files = () if tree else sorted(results.items()) + lines.extend(overview) + modified_files = () if terminal else sorted(results.items()) for rel, r in modified_files: if r['status'] == 'real-change': n_real = sum(1 for h in r['hunks'] if h['kind'] == 'real') @@ -213,7 +183,7 @@ def summary_lines(results, counts, tree=False): lines.append(' MODIFIED {} ({} hunk(s){})'.format( rel, n_real, ', {} moved'.format(n_moved) if n_moved else '')) - if tree: + if terminal: lines.append('') lines.append('AUTOSAR / A2L changes:') semantic_start = len(lines) @@ -266,7 +236,7 @@ def summary_lines(results, counts, tree=False): for rel, n, kind in a2l_removed: lines.append(' - {} ({}) in {}'.format(n, kind, rel)) - if tree and len(lines) == semantic_start: + if terminal and len(lines) == semantic_start: lines.append(' No extracted AUTOSAR/A2L changes.') # cross-artifact heads-up: a model whose ARXML and C did not change @@ -279,6 +249,105 @@ def summary_lines(results, counts, tree=False): return lines +_HUNK_KIND_ORDER = ( + 'real', 'moved', 'comment', 'rename', 'assumed-rename', 'reorder', + 'uuid', 'timestamp', 'sw-version', 'description', 'whitespace', 'mixed', +) + + +def diagnostic_lines(results, arxml_only=False, exclude=(), user_rules=(), + skip_var_renames=False, source_kinds=('folder', 'folder')): + """Technical terminal trace included by ``--no-report``. + + The trace explains the effective compare settings, aggregate hunk + classifications and one summary line per non-identical path without + printing source content. It is deliberately derived from the raw scan + result so it cannot change a verdict, count or exit code. + """ + hunk_counts = Counter( + h.get('kind', 'unknown') + for result in results.values() + for h in result.get('hunks', ()) + ) + differences = sum( + result['status'] not in ('identical', 'error') + for result in results.values() + ) + errors = sum(result['status'] == 'error' for result in results.values()) + binary_changes = sum( + bool(result.get('binary')) for result in results.values() + ) + rule_names = [rule.name for rule in user_rules] + + lines = [ + '', + 'Diagnostics:', + ' Runtime: codegen-compare-tool {} | Python {} | {}'.format( + __version__, platform.python_version(), sys.platform), + ' Sources: BASELINE {}; CURRENT {}'.format(*source_kinds), + ' Scope: {}'.format( + 'ARXML/XML/A2L only' if arxml_only else 'all matched files'), + ' Excludes: {}'.format(', '.join(exclude) if exclude else 'none'), + ' Custom noise rules: {}'.format( + ', '.join(rule_names) if rule_names else 'none'), + ' Unsafe rename quick check: {}'.format( + 'enabled' if skip_var_renames else 'disabled'), + ' Paths: {} scanned; {} with differences; {} not compared; ' + '{} binary change(s)'.format( + len(results), differences, errors, binary_changes), + ] + + lines.append('Hunk classification: {} total'.format(sum(hunk_counts.values()))) + if hunk_counts: + order = {kind: index for index, kind in enumerate(_HUNK_KIND_ORDER)} + for kind, count in sorted( + hunk_counts.items(), + key=lambda item: (order.get(item[0], len(order)), item[0])): + lines.append(' {}: {}'.format(kind, count)) + else: + lines.append(' (no text hunks; one-sided, binary or identical files only)') + + order = {kind: index for index, kind in enumerate(_HUNK_KIND_ORDER)} + + lines.append('File summary:') + traced = [(rel, result) for rel, result in sorted(results.items()) + if result['status'] != 'identical'] + if not traced: + lines.append(' (no differences or compare errors)') + return lines + + for rel, result in traced: + hunks = result.get('hunks', ()) + content = 'binary' if result.get('binary') else 'text' + per_file = Counter(h.get('kind', 'unknown') for h in hunks) + hunk_summary = ', '.join( + '{}={}'.format(kind, count) + for kind, count in sorted( + per_file.items(), + key=lambda item: (order.get(item[0], len(order)), item[0]))) + details = [ + 'ruleset={}'.format(ruleset_for(rel)), + content, + '{} hunk(s){}'.format( + len(hunks), ': ' + hunk_summary if hunk_summary else ''), + ] + if result.get('renames'): + details.append('{} rename(s)'.format(len(result['renames']))) + if result.get('moved_from'): + details.append('move from BASELINE {} ({:.1%}, {})'.format( + result['moved_from'], result.get('move_similarity', 0), + result.get('move_status', 'unknown'))) + if result.get('moved_to'): + details.append('move to CURRENT {} ({:.1%}, {})'.format( + result['moved_to'], result.get('move_similarity', 0), + result.get('move_status', 'unknown'))) + if result.get('notes'): + details.append('notes: {}'.format('; '.join(result['notes']))) + lines.append(' {} [{}] {}'.format( + rel, result['status'], '; '.join(details))) + return lines + + def _parser(): ap = argparse.ArgumentParser( prog='compare_tool', @@ -304,10 +373,11 @@ def _parser(): help='HTML report output path (default: compare_report.html, ' 'or arxml_update.html with --arxml-only)') reports.add_argument('--no-report', action='store_true', - help='terminal summary only: show every scanned file ' - 'in a folder tree with its verdict, plus AUTOSAR ' - 'and A2L changes, without code diffs or an HTML ' - 'report. Requires both input paths; existing ' + help='terminal summary and diagnostics only: show the ' + 'model Overview, AUTOSAR/A2L changes, effective ' + 'settings, hunk-kind totals and one summary per ' + 'non-identical file, without source diffs or an ' + 'HTML report. Requires both input paths; existing ' 'reports are left untouched') ap.add_argument('--arxml-only', action='store_true', help='compare only ARXML/XML and A2L files and write a ' @@ -566,8 +636,16 @@ def progress(done, total, rel): # normal outcome, and a run that produced no report must not be # indistinguishable from it (--exit-zero cannot mask this either) return 2 - for line in summary_lines(results, counts, tree=args.no_report): + for line in summary_lines(results, counts, terminal=args.no_report): print(line) + if args.no_report: + for line in diagnostic_lines( + results, arxml_only=args.arxml_only, exclude=args.exclude, + user_rules=user_rules, + skip_var_renames=args.skip_var_renames, + source_kinds=('zip' if old_zip else 'folder', + 'zip' if new_zip else 'folder')): + print(line) if out is not None and args.arxml_only: if counts['real-change'] or counts['added'] or counts['deleted']: diff --git a/docs/architecture.md b/docs/architecture.md index f9ae93e..e33954b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -140,7 +140,7 @@ Do not copy mappings or rollup logic into CLI, HTML or Qt code. `run_compare` removes a stale target report before scanning. A report-write failure preserves the scan for terminal output and exits `2`. -With `--no-report`, `run_compare` skips HTML rendering. `summary_lines(..., tree=True)` prints the Overview, full tree, semantic details and warnings without source hunks. `report.model_overview` supplies the same structured rows to HTML and terminal renderers. +With `--no-report`, `run_compare` skips HTML rendering. `summary_lines(..., terminal=True)` prints the Overview, semantic details and warnings without source hunks. `diagnostic_lines` adds effective settings, aggregate hunk-kind totals and one summary per non-identical file. Both consume the raw result and never change verdicts, counts or exit codes. `report.model_overview` supplies the same structured rows to HTML and terminal renderers. ### Viewer diff --git a/docs/usage.md b/docs/usage.md index 6628f56..4201bf7 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -36,12 +36,16 @@ This mode creates no HTML and prints no source-code hunks. It prints: 1. total verdict counts; 2. a per-model Overview with file counts and AUTOSAR changes; -3. a complete folder tree with one verdict per file; -4. detailed AUTOSAR/A2L changes; -5. consistency and quick-check warnings. +3. detailed AUTOSAR/A2L changes; +4. consistency and quick-check warnings; +5. tool/Python/platform versions and effective compare options; +6. totals for each hunk classification; +7. one summary per non-identical file with its verdict, ruleset, hunk-kind counts, rename/move totals and error notes. An existing report is left untouched. `--json` and `--sarif` still write their requested files. +The diagnostics do not print source content and do not change verdicts or exit codes. + ### Common options | Option | Purpose | diff --git a/docs/vi/architecture.md b/docs/vi/architecture.md index 01d0aaf..3bca78f 100644 --- a/docs/vi/architecture.md +++ b/docs/vi/architecture.md @@ -125,7 +125,7 @@ Không copy mapping hoặc rollup logic vào CLI, HTML hay Qt renderer. `main.viewer_requested` chọn Qt hoặc terminal comparison. `run_compare` xóa stale report trước khi scan; lỗi ghi report vẫn in được scan result và trả exit code `2`. -Với `--no-report`, tool bỏ qua HTML rendering. `summary_lines(..., tree=True)` in Overview, full tree, semantic details và warnings. `report.model_overview` cấp cùng structured rows cho HTML và terminal. +Với `--no-report`, tool bỏ qua HTML rendering. `summary_lines(..., terminal=True)` in Overview, semantic details và warnings mà không in source hunk. `diagnostic_lines` bổ sung effective settings, tổng số hunk theo kind và một summary cho mỗi file không identical. Cả hai đều dùng raw result và không thay đổi verdict, count hoặc exit code. `report.model_overview` cấp cùng structured rows cho HTML và terminal. Viewer chạy scanner trong `qtviewer/worker.py` bằng `QThread`. Export report luôn dùng raw scan, không dùng filtered tree. diff --git a/docs/vi/usage.md b/docs/vi/usage.md index b26b5bf..ce30252 100644 --- a/docs/vi/usage.md +++ b/docs/vi/usage.md @@ -36,12 +36,16 @@ Mode này không tạo HTML và không in source-code hunk. Terminal hiển th 1. tổng số file theo verdict; 2. Overview theo model với file count và AUTOSAR changes; -3. folder tree đầy đủ với verdict của từng file; -4. AUTOSAR/A2L changes chi tiết; -5. consistency và quick-check warnings. +3. AUTOSAR/A2L changes chi tiết; +4. consistency và quick-check warnings; +5. version của tool/Python/platform và các compare option thực tế; +6. tổng số theo từng hunk classification; +7. một summary cho mỗi file không identical gồm verdict, ruleset, số hunk theo kind, tổng rename/move và error note. Report đã có từ trước không bị thay đổi. `--json` và `--sarif` vẫn tạo file khi được chỉ định. +Diagnostics không in source content và không thay đổi verdict hoặc exit code. + ### Options thường dùng | Option | Công dụng | diff --git a/tests/test_cli_modes.py b/tests/test_cli_modes.py index ae15996..e93427f 100644 --- a/tests/test_cli_modes.py +++ b/tests/test_cli_modes.py @@ -14,7 +14,7 @@ from pathlib import Path from unittest import mock -from compare_tool.main import main, viewer_requested +from compare_tool.main import diagnostic_lines, main, viewer_requested def quiet(fn, *a): @@ -198,7 +198,10 @@ def test_no_report_generation_or_existing_report_changes(self): code, output, errors = self._run() self.assertEqual(code, 0) self.assertEqual(errors, '') - self.assertIn('same.c [identical]', output) + self.assertIn('Diagnostics:', output) + self.assertIn('File summary:', output) + self.assertIn('(no differences or compare errors)', output) + self.assertNotIn('same.c [identical]', output) self.assertIn('No extracted AUTOSAR/A2L changes.', output) self.assertNotIn('Report written:', output) self.assertEqual(stale.read_text(encoding='utf-8'), 'existing report') @@ -206,7 +209,7 @@ def test_no_report_generation_or_existing_report_changes(self): report.assert_not_called() arxml_report.assert_not_called() - def test_tree_includes_every_verdict_without_code_or_hunk_details(self): + def test_file_summary_includes_non_identical_verdicts_without_hunk_details(self): pairs = { 'model/changed.c': ('int value = 1;\n', 'int value = 2;\n'), 'model/same.h': ('int same;\n', 'int same;\n'), @@ -221,15 +224,17 @@ def test_tree_includes_every_verdict_without_code_or_hunk_details(self): (self.new / 'bad.txt').write_bytes(b'\xff\xfe\x41') code, output, _ = self._run() self.assertEqual(code, 2) - for name, status in (('changed.c', 'modified'), ('same.h', 'identical'), + for name, status in (('changed.c', 'real-change'), ('comment.c', 'comment-only'), ('noise.c', 'ignorable-only'), ('removed.txt', 'deleted'), ('added.txt', 'added'), ('bad.txt', 'error')): self.assertIn('{} [{}]'.format(name, status), output) - self.assertIn('|-- model/\n| |-- changed.c [modified]', output) + self.assertNotIn('same.h [identical]', output) + self.assertNotIn('Folder tree:', output) self.assertIn('COMPARE INCOMPLETE', output) self.assertNotIn('int value', output) - self.assertNotIn('hunk(s)', output) + self.assertNotIn('hunk 1:', output) + self.assertNotIn('BASELINE line', output) def test_autosar_and_a2l_summaries_use_the_scan(self): from compare_tool.main import summary_lines @@ -258,18 +263,19 @@ def test_autosar_and_a2l_summaries_use_the_scan(self): ) for row in expected_rows: self.assertIn(row, output) - self.assertLess(output.index('Overview:'), output.index('Folder tree:')) + self.assertLess(output.index('Overview:'), output.index('Diagnostics:')) for line in summary_lines(results, summarize(results)): if not line.startswith(' MODIFIED'): self.assertIn(line, output) - def test_flat_tree_has_no_model_overview(self): + def test_flat_scan_has_no_model_overview_or_folder_tree(self): for side in (self.old, self.new): self._file(side, 'same.c', 'int same;\n') code, output, _ = self._run() self.assertEqual(code, 0) self.assertNotIn('Overview:', output) - self.assertIn('Folder tree:', output) + self.assertNotIn('Folder tree:', output) + self.assertIn('Diagnostics:', output) def test_report_consistency_warnings_remain_visible_with_exit_zero(self): from compare_tool.report import consistency_advisories @@ -293,7 +299,8 @@ def test_filters_and_no_report_work_together(self): self.assertEqual(code, 1) self.assertIn('model.arxml [added]', output) self.assertNotIn('model.c', output) - self.assertNotIn('skip.a2l', output) + self.assertNotIn('skip.a2l [', output) + self.assertIn('Excludes: skip.a2l', output) self.assertNotIn('report written', output) def test_exit_zero_only_suppresses_real_changes(self): @@ -303,10 +310,11 @@ def test_exit_zero_only_suppresses_real_changes(self): (self.new / 'bad.txt').write_bytes(b'\xff\xfe\x41') self.assertEqual(self._run('--exit-zero')[0], 2) - def test_empty_filtered_tree_is_explicit(self): + def test_empty_scan_is_explicit(self): code, output, _ = self._run() self.assertEqual(code, 0) - self.assertIn('(no files matched)', output) + self.assertIn('Paths: 0 scanned', output) + self.assertIn('(no differences or compare errors)', output) self.assertIn('Summary: 0 modified', output) def test_json_and_sarif_remain_opt_in(self): @@ -318,6 +326,22 @@ def test_json_and_sarif_remain_opt_in(self): self.assertEqual(json.loads(sarif_path.read_text(encoding='utf-8'))['version'], '2.1.0') self.assertEqual(list(self.root.glob('*.html')), []) + def test_no_report_always_includes_diagnostics_without_hunk_details(self): + self._file(self.old, 'model.c', 'int value = 1;\n') + self._file(self.new, 'model.c', 'int value = 2;\n') + code, output, errors = self._run('--exclude', '*.tmp') + self.assertEqual(code, 1) + self.assertEqual(errors, '') + self.assertIn('Diagnostics:', output) + self.assertIn('Scope: all matched files', output) + self.assertIn('Excludes: *.tmp', output) + self.assertIn('Hunk classification:', output) + self.assertIn('real: 1', output) + self.assertIn('model.c [real-change] ruleset=c; text; ' + '1 hunk(s): real=1', output) + self.assertNotIn('hunk 1:', output) + self.assertNotIn('int value =', output) + def test_rules_and_quick_check_are_still_applied(self): self._file(self.old, 'model.cpp', 'out = input_a;\n') self._file(self.new, 'model.cpp', 'out = input_b;\n') @@ -336,7 +360,9 @@ def test_rules_and_quick_check_are_still_applied(self): def test_invalid_combinations_keep_console_and_raise_usage_error(self): for argv in (['--no-report'], ['old', '--no-report'], ['old', 'new', '--no-report', '--qt'], - ['old', 'new', '--no-report', '--report', 'out.html']): + ['old', 'new', '--no-report', '--report', 'out.html'], + ['--diagnostics'], ['old', '--diagnostics'], + ['old', 'new', '--diagnostics', '--qt']): with self.subTest(argv=argv): self.assertFalse(quiet(viewer_requested, argv)) with self.assertRaises(SystemExit) as raised: @@ -353,7 +379,8 @@ def test_zip_sources_and_side_labels(self): self.assertEqual(code, 0) self.assertIn('BASELINE: build 100', output) self.assertIn('CURRENT: build 101', output) - self.assertIn('same.c [identical]', output) + self.assertIn('Sources: BASELINE zip; CURRENT zip', output) + self.assertNotIn('same.c [identical]', output) class TestVersionFlag(unittest.TestCase): @@ -366,6 +393,56 @@ def test_version_prints_the_package_version_and_exits_zero(self): self.assertIn(__version__, out.getvalue()) +class TestDiagnosticLines(unittest.TestCase): + def test_reports_settings_hunks_moves_renames_and_errors(self): + class Rule: + name = 'build-stamp' + + results = { + 'a.c': { + 'status': 'real-change', 'binary': False, + 'hunks': [ + {'kind': 'real', 'old_range': [1, 2], 'new_range': [1, 2]}, + {'kind': 'moved', 'old_range': [5, 7], 'new_range': [5, 5], + 'moved_to': 20}, + ], + 'renames': {'old_name': 'new_name'}, + 'notes': ['review this file'], + }, + 'moved/new.c': { + 'status': 'added', 'binary': False, 'hunks': [], 'renames': {}, + 'notes': [], 'moved_from': 'old/new.c', + 'move_similarity': 0.925, 'move_status': 'comment-only', + }, + 'bad.arxml': { + 'status': 'error', 'binary': False, 'hunks': [], 'renames': {}, + 'notes': ['compare failed: PermissionError'], + }, + 'same.h': { + 'status': 'identical', 'binary': False, 'hunks': [], + 'renames': {}, 'notes': [], + }, + } + text = '\n'.join(diagnostic_lines( + results, arxml_only=True, exclude=('generated/*',), + user_rules=(Rule(),), skip_var_renames=True, + source_kinds=('zip', 'folder'))) + self.assertIn('Sources: BASELINE zip; CURRENT folder', text) + self.assertIn('Scope: ARXML/XML/A2L only', text) + self.assertIn('Custom noise rules: build-stamp', text) + self.assertIn('Unsafe rename quick check: enabled', text) + self.assertIn('Paths: 4 scanned; 2 with differences; 1 not compared', text) + self.assertIn('Hunk classification: 2 total', text) + self.assertIn('moved: 1', text) + self.assertIn('a.c [real-change] ruleset=c; text; ' + '2 hunk(s): real=1, moved=1; 1 rename(s); ' + 'notes: review this file', text) + self.assertIn('move from BASELINE old/new.c (92.5%, comment-only)', text) + self.assertIn('notes: compare failed: PermissionError', text) + self.assertNotIn('hunk 1:', text) + self.assertNotIn('same.h', text) + + class TestTkinterPanelIsGone(unittest.TestCase): def test_gui_flag_is_rejected(self): self.assertFalse(quiet(viewer_requested, ['--gui'])) @@ -376,6 +453,11 @@ def test_the_module_is_not_shipped(self): with self.assertRaises(ImportError): __import__('compare_tool.gui') + def test_removed_diagnostics_flag_is_rejected(self): + self.assertFalse(quiet(viewer_requested, ['--diagnostics'])) + with self.assertRaises(SystemExit): + quiet(main, ['old', 'new', '--diagnostics']) + if __name__ == '__main__': unittest.main()