# -*- coding: utf-8 -*- """The declared scope: what the tool is answerable for, and what it says about everything else. Every mark here is a placeholder. `PNL` stands for the pieces in scope and `XX` / `YY` for somebody else's -- the real prefixes on a real job are job information and belong in the job knowledge file, never in this repository. What these tests are actually protecting, in one sentence each: * an undeclared scope judges EVERYTHING (an empty list is not an empty scope, and this is the assertion that stops it becoming one); * a mark whose prefix cannot be read is never set aside; * the report line exists in all three states and never reads as a pass; * nothing the tool sets aside is summed away without being named; * a NEAR MISS -- a set-aside prefix that is one letter off a declared one -- is reported and never resolved. `PNLA` is used for that throughout: it is `PNL` plus a letter, which is the shape that made this a hole. """ import pytest from vibe import scope as S # ------------------------------------------------------------ the predicate def test_no_declaration_includes_everything(): """The single most important behaviour here. 'Nobody said' must never collapse into 'nothing is in scope'.""" empty = S.Scope() assert empty.is_declared is False for name in ('PNL001', 'XX001', 'YY9', '', None, '12345'): assert empty.includes(name) is True def test_declared_scope_keeps_its_own_and_sets_the_rest_aside(): scope = S.Scope(prefixes=('PNL',)) assert scope.includes('PNL001') is True assert scope.includes('pnl001') is True # case is not a fact assert scope.includes('XX001') is False assert scope.includes('YY12') is False def test_several_prefixes_may_be_declared(): scope = S.Scope(prefixes=('PNL', 'XX')) assert scope.includes('XX001') is True assert scope.includes('YY001') is False def test_a_mark_with_no_readable_prefix_is_never_set_aside(): """'I could not tell' may only ever resolve towards judging it.""" scope = S.Scope(prefixes=('PNL',)) assert scope.includes('12345') is True assert scope.includes('') is True assert scope.includes(None) is True assert scope.is_unreadable('12345') is True assert scope.kind('12345') == S.UNREADABLE def test_blank_and_duplicate_prefixes_are_cleaned_not_honoured(): scope = S.Scope(prefixes=['PNL', ' pnl ', '', None]) assert scope.prefixes == ('PNL',) def test_an_all_blank_declaration_is_no_declaration(): assert S.Scope(prefixes=['', ' ']).is_declared is False # ------------------------------------------------------------- mark shapes @pytest.mark.parametrize('name,expected', [ ('PNL001', True), ('PNL 1', True), ('XX12', True), ('GENERAL ARRANGEMENT', False), ('', False), (None, False), ('12345', False), ]) def test_looks_like_mark(name, expected): """A sheet name that is not a mark is not somebody else's piece, and must not be counted as one.""" assert S.looks_like_mark(name) is expected def test_as_mark_hands_back_nothing_for_a_non_mark(): scope = S.Scope(prefixes=('PNL',)) assert scope.includes(S.as_mark('GENERAL ARRANGEMENT')) is True # ----------------------------------------------------------------- splits def test_split_preserves_order_and_keeps_both_halves(): scope = S.Scope(prefixes=('PNL',)) inside, outside = scope.split_names( ['PNL001', 'XX001', 'PNL002', 'YY001']) assert inside == ['PNL001', 'PNL002'] assert outside == ['XX001', 'YY001'] def test_split_reads_a_key(): scope = S.Scope(prefixes=('PNL',)) rows = [{'mark': 'PNL001'}, {'mark': 'XX001'}] inside, outside = scope.split(rows, key=lambda r: r['mark']) assert inside == [{'mark': 'PNL001'}] assert outside == [{'mark': 'XX001'}] def test_tally_names_every_kind_biggest_first(): """Counted AND named. '435 pieces' sends nobody anywhere.""" scope = S.Scope(prefixes=('PNL',)) assert scope.tally(['XX1', 'XX2', 'YY1', 'XX3', 'ZZ1', 'ZZ2']) == [ ('XX', 3), ('ZZ', 2), ('YY', 1)] # ------------------------------------------------------------ the reporting def test_the_line_when_work_was_set_aside_names_the_kinds(): scope = S.Scope(prefixes=('PNL',)) line = scope.line(['XX1', 'XX2', 'YY1'], total=10) assert 'NOT JUDGED, OUT OF SCOPE' in line assert 'XX 2' in line and 'YY 1' in line assert '3 of 10' in line # Never a pass, and never a failure -- it says both out loud. assert 'Not judged is not clean, and not a failure either.' in line def test_the_line_is_printed_even_when_nothing_was_set_aside(): """A run that filtered nothing looks exactly like a run that filtered everything once the answer is on the screen.""" line = S.Scope(prefixes=('PNL',)).line([]) assert 'none' in line assert 'declared scope is piece marks beginning PNL' in line def test_the_undeclared_line_says_everything_was_judged_and_why_that_matters(): line = S.Scope().line([]) assert 'No scope is declared' in line assert 'every piece was judged' in line assert 'never been validated' in line def test_the_line_reports_unreadable_marks_separately(): scope = S.Scope(prefixes=('PNL',)) line = scope.line(['XX1'], total=3, unreadable_names=['12345']) assert 'no readable mark prefix' in line assert 'judged rather than set aside' in line def test_the_annotation_is_for_work_the_user_picked_himself(): """Nothing the user selects is ever dropped. It is judged, and the report says the verdict rests on unvalidated knowledge.""" scope = S.Scope(prefixes=('PNL',)) note = scope.annotation(['XX1']) assert 'AT YOUR REQUEST' in note assert 'not been validated' in note assert scope.annotation([]) is None assert S.Scope().annotation(['XX1']) is None # nothing declared, nothing odd def test_label_reads_as_a_sentence_a_person_can_check(): assert S.Scope(prefixes=('PNL',)).label() == 'piece marks beginning PNL' assert S.Scope(prefixes=('PNL', 'XX')).label() == \ 'piece marks beginning PNL or XX' assert S.Scope().label() == 'no scope declared' def test_declaration_lines_carry_who_said_so(): scope = S.Scope(prefixes=('PNL',), declared_by='somebody', note='because of a reason') assert scope.declaration_lines() == ['scope declared by somebody', 'because of a reason'] assert S.Scope(prefixes=('PNL',)).declaration_lines() == [] # --------------------------------------------------------- the near miss # # The hole: `prefix_of` reads the whole leading letter run, so a declared # `PNL` does not reach `PNLA001` and that piece is handed to another # engineer on the strength of one letter. The decision recorded here is that # the MATCH IS NOT WIDENED and the near miss is REPORTED -- widening would # also swallow every other run starting `PNL`, and a false in-scope is judged # silently while a false out-of-scope is at least printed. def test_the_match_is_still_the_whole_letter_run_and_was_not_widened(): """The regression lock on the decision. If somebody later widens the match, they must come here, read why it was not, and change it on purpose -- with the owner's answer in hand, not instead of it.""" scope = S.Scope(prefixes=('PNL',)) assert scope.includes('PNLA001') is False assert scope.includes('PNLZ001') is False assert scope.prefixes == ('PNL',) def test_a_nearly_matching_prefix_is_named_as_a_near_miss(): scope = S.Scope(prefixes=('PNL',)) assert scope.near_misses(['PNLA1', 'PNLA2', 'XX1']) == [('PNLA', 2, 'PNL')] def test_a_near_miss_is_found_in_both_directions(): """Either spelling may be the longer one. Declaring `PNLA` and finding `PNL001` is the same mistake seen from the other end.""" assert S.Scope(prefixes=('PNLA',)).near_misses(['PNL1']) == \ [('PNL', 1, 'PNLA')] def test_an_exact_match_is_not_a_near_miss_and_neither_is_an_unrelated_one(): scope = S.Scope(prefixes=('PNL',)) assert scope.nearly('PNL') is None # it is a match, not a near one assert scope.nearly('XX') is None assert scope.near_misses(['XX1', 'YY2']) == [] def test_an_unreadable_mark_is_never_a_near_miss(): """It has no prefix to be nearly anything, and it was judged anyway.""" scope = S.Scope(prefixes=('PNL',)) assert scope.nearly(S.UNREADABLE) is None assert scope.near_misses(['12345']) == [] def test_nothing_is_a_near_miss_when_nothing_was_declared(): assert S.Scope().near_misses(['PNLA1']) == [] assert S.Scope().near_miss_line(['PNLA1']) is None def test_the_near_miss_line_says_both_spellings_and_refuses_to_guess(): line = S.Scope(prefixes=('PNL',)).near_miss_line(['PNLA1'], unit='piece') assert 'PNLA' in line and 'PNL' in line assert 'NEAR MISS' in line assert 'will not guess' in line # It names the fix without making it: the human widens the declaration. assert 'scope.mark_prefixes' in line def test_the_near_miss_rides_on_the_ordinary_report_line(): """So that every existing consumer of `line()` reports it without having been changed to know it exists.""" line = S.Scope(prefixes=('PNL',)).line(['PNLA1', 'XX1'], total=9) assert 'NOT JUDGED, OUT OF SCOPE' in line assert 'NEAR MISS' in line assert 'PNLA 1' in line # still tallied by kind as before def test_an_ordinary_out_of_scope_line_carries_no_near_miss_noise(): line = S.Scope(prefixes=('PNL',)).line(['XX1', 'YY2'], total=9) assert 'NEAR MISS' not in line def test_the_annotation_carries_the_near_miss_too(): """Here it matters more: the reader is being told his own selection rests on unvalidated knowledge, and that warning may be the wrong one.""" note = S.Scope(prefixes=('PNL',)).annotation(['PNLA1']) assert 'AT YOUR REQUEST' in note assert 'NEAR MISS' in note # ------------------------------------------------------- the short headline def test_the_short_line_says_the_count_in_all_three_states(): scope = S.Scope(prefixes=('PNL',)) assert 'none declared' in S.Scope().short_line(['XX1']) assert 'no piece' in scope.short_line([], total=9) busy = scope.short_line(['XX1', 'XX2', 'YY1'], total=9, unit='assembly') assert 'NOT JUDGED, OUT OF SCOPE' in busy assert '3 of 9 assembly(s)' in busy assert 'XX 2' in busy and 'YY 1' in busy def test_the_short_line_caps_the_kinds_so_it_stays_short(): scope = S.Scope(prefixes=('PNL',)) text = scope.short_line(['AA1', 'BB1', 'CC1', 'DD1', 'EE1']) assert '+2 more' in text assert len(text) < 200 def test_the_short_line_flags_a_near_miss_without_explaining_it(): """It is a pointer at the full sentence, not a replacement for it.""" text = S.Scope(prefixes=('PNL',)).short_line(['PNLA1']) assert text.endswith('-- NEAR MISS') assert 'will not guess' not in text # ------------------------------------------------------- reading the layer def _get(know, dotted, default=None): node = know for part in dotted.split('.'): if not isinstance(node, dict) or part not in node: return default node = node[part] return default if node is None else node def test_of_reads_the_job_key(): know = {'scope': {'mark_prefixes': ['PNL'], 'declared_by': 'somebody', 'note': 'a reason'}} scope = S.of(know, _get) assert scope.prefixes == ('PNL',) assert scope.declared_by == 'somebody' assert scope.note == 'a reason' def test_of_accepts_a_single_scalar_because_yaml_readers_hand_back_strings(): assert S.of({'scope': {'mark_prefixes': 'PNL'}}, _get).prefixes == ('PNL',) def test_of_on_an_absent_or_empty_key_declares_nothing(): for know in ({}, None, {'scope': {}}, {'scope': {'mark_prefixes': []}}, {'scope': {'mark_prefixes': None}}): assert S.of(know, _get).is_declared is False def test_the_shipped_default_declares_nothing(): """The repository must never carry one job's division of labour.""" from vibe import knowledge assert knowledge.DEFAULTS['scope']['mark_prefixes'] == [] assert S.of(knowledge.DEFAULTS, knowledge.get).is_declared is False def test_a_folded_yaml_note_does_not_carry_its_newline_into_the_report(): scope = S.Scope(prefixes=('PNL',), note='one sentence.\n') assert scope.declaration_lines() == ['one sentence.']