Skip to content

Commit 87456e4

Browse files
author
Your Name
committed
Add more unit tests for Match
1 parent d0eed5d commit 87456e4

2 files changed

Lines changed: 293 additions & 43 deletions

File tree

test/addon/conftest.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,39 @@ class Shape {
122122
myint bar(void) { myint y = 3; return y; }
123123
"""
124124

125+
MATCH_C = """struct Point {
126+
int x;
127+
int y;
128+
};
129+
130+
int calc(int a, int b)
131+
{
132+
int bit_or = a | b;
133+
int log_or = a || b;
134+
int mod = a % b;
135+
int mul = a * b;
136+
int not_a = !a;
137+
int neq = a != b;
138+
int arr[3];
139+
arr[0] = a;
140+
a += 1;
141+
if (a > b) {
142+
return a;
143+
}
144+
return calc(a, b);
145+
}
146+
"""
147+
148+
MATCH_CPP = """class Widget {};
149+
150+
bool use(int i, int j)
151+
{
152+
std::vector<int> v;
153+
bool less = i < j;
154+
return less && v.empty();
155+
}
156+
"""
157+
125158

126159
@pytest.fixture(scope='session')
127160
def sample_data(dump_factory):
@@ -153,3 +186,15 @@ def sample_cpp_cfg(sample_cpp_data):
153186
def multi_cfg_data(dump_factory):
154187
"""Parsed dump of a file with two preprocessor configurations."""
155188
return dump_factory.parse(MULTI_CFG_C, filename='multi.c')
189+
190+
191+
@pytest.fixture(scope='session')
192+
def match_cfg(dump_factory):
193+
"""Configuration of a C sample covering the match() pattern syntax."""
194+
return dump_factory.parse(MATCH_C, filename='match.c').configurations[0]
195+
196+
197+
@pytest.fixture(scope='session')
198+
def match_cpp_cfg(dump_factory):
199+
"""Configuration of a C++ sample with linked '<' tokens for match()."""
200+
return dump_factory.parse(MATCH_CPP, filename='match.cpp').configurations[0]

test/addon/test_cppcheckdata.py

Lines changed: 248 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -484,59 +484,264 @@ def test_astIsFloat(self, sample_cfg):
484484

485485

486486
class TestMatch:
487-
def test_simpleMatch(self, sample_cfg):
488-
add_def = find_token(sample_cfg, 'add')
489-
assert cppcheckdata.simpleMatch(add_def, 'add ( int a , int b )')
490-
assert not cppcheckdata.simpleMatch(add_def, 'add ( int b')
491-
assert not cppcheckdata.simpleMatch(None, 'add')
487+
"""Tests for the cppcheckdata.match()/simpleMatch() pattern matching."""
488+
489+
def test_simpleMatch(self, match_cfg):
490+
calc_def = find_token(match_cfg, 'calc')
491+
assert cppcheckdata.simpleMatch(calc_def, 'calc')
492+
assert cppcheckdata.simpleMatch(calc_def, 'calc ( int a , int b )')
493+
assert not cppcheckdata.simpleMatch(calc_def, 'calc ( int b')
494+
assert not cppcheckdata.simpleMatch(None, 'calc')
495+
496+
def test_literal_sequence(self, match_cfg):
497+
calc_def = find_token(match_cfg, 'calc')
498+
assert cppcheckdata.match(calc_def, 'calc ( int')
499+
assert cppcheckdata.match(calc_def, 'calc ( int a , int b )')
500+
assert not cppcheckdata.match(calc_def, 'calc ( char')
501+
# the match is anchored at the given token
502+
assert not cppcheckdata.match(calc_def, 'int calc')
503+
504+
def test_empty_pattern_and_no_token(self, match_cfg):
505+
calc_def = find_token(match_cfg, 'calc')
506+
assert not cppcheckdata.match(calc_def, '')
507+
assert not cppcheckdata.match(None, 'calc')
508+
509+
def test_end(self, match_cfg):
510+
calc_def = find_token(match_cfg, 'calc')
511+
res = cppcheckdata.match(calc_def, 'calc')
512+
assert res.end is calc_def
513+
res = cppcheckdata.match(calc_def, 'calc ( int')
514+
assert res.end.str == 'int'
492515

493-
def test_match_plain(self, sample_cfg):
494-
add_def = find_token(sample_cfg, 'add')
495-
res = cppcheckdata.match(add_def, 'add ( int')
516+
# ---- literal operator tokens ----
517+
518+
def test_literal_bitwise_or(self, match_cfg):
519+
# a literal '|' in the pattern matches a '|' token ...
520+
bit_or = find_token(match_cfg, '|')
521+
assert cppcheckdata.match(bit_or, '|')
522+
assert cppcheckdata.match(bit_or.previous, '%var% | %var% ;')
523+
# ... but not a '||' token, and it is not an either-or alternation
524+
log_or = find_token(match_cfg, '||')
525+
assert not cppcheckdata.match(log_or, '|')
526+
527+
def test_literal_logical_or(self, match_cfg):
528+
log_or = find_token(match_cfg, '||')
529+
assert cppcheckdata.match(log_or, '||')
530+
assert cppcheckdata.match(log_or.previous, '%var% || %var% ;')
531+
bit_or = find_token(match_cfg, '|')
532+
assert not cppcheckdata.match(bit_or, '||')
533+
534+
def test_literal_not(self, match_cfg):
535+
not_tok = find_token(match_cfg, '!')
536+
assert cppcheckdata.match(not_tok, '! %var% ;')
537+
assert not cppcheckdata.match(find_token(match_cfg, '!='), '!')
538+
539+
def test_literal_not_equal(self, match_cfg):
540+
neq = find_token(match_cfg, '!=')
541+
assert cppcheckdata.match(neq, '!=')
542+
assert cppcheckdata.match(neq.previous, '%var% != %var% ;')
543+
assert not cppcheckdata.match(find_token(match_cfg, '='), '!=')
544+
assert not cppcheckdata.match(neq, '=')
545+
546+
def test_literal_star(self, match_cfg):
547+
mul = find_token(match_cfg, '*')
548+
assert cppcheckdata.match(mul, '*')
549+
assert cppcheckdata.match(mul.previous, '%var% * %var% ;')
550+
551+
def test_literal_percent(self, match_cfg):
552+
mod = find_token(match_cfg, '%')
553+
assert cppcheckdata.match(mod, '%')
554+
assert cppcheckdata.match(mod.previous, '%var% % %var% ;')
555+
556+
def test_literal_parentheses(self, match_cfg):
557+
calc_call = find_token(match_cfg, 'calc', skip=1)
558+
assert cppcheckdata.match(calc_call, 'calc ( %var% , %var% ) ;')
559+
560+
# ---- %keyword% patterns ----
561+
562+
def test_any(self, match_cfg):
563+
for token_str in ('calc', '(', '3', ';', '|', '{'):
564+
assert cppcheckdata.match(find_token(match_cfg, token_str), '%any%')
565+
plus_assign = find_token(match_cfg, '+=')
566+
assert cppcheckdata.match(plus_assign, '%assign% %any% ;')
567+
568+
def test_assign(self, match_cfg):
569+
assert cppcheckdata.match(find_token(match_cfg, '='), '%assign%')
570+
assert cppcheckdata.match(find_token(match_cfg, '+='), '%assign%')
571+
assert not cppcheckdata.match(find_token(match_cfg, '!='), '%assign%')
572+
assert not cppcheckdata.match(find_token(match_cfg, '|'), '%assign%')
573+
574+
def test_comp(self, match_cfg):
575+
assert cppcheckdata.match(find_token(match_cfg, '>'), '%comp%')
576+
assert cppcheckdata.match(find_token(match_cfg, '!='), '%comp%')
577+
assert not cppcheckdata.match(find_token(match_cfg, '='), '%comp%')
578+
assert not cppcheckdata.match(find_token(match_cfg, '|'), '%comp%')
579+
580+
def test_name(self, match_cfg):
581+
assert cppcheckdata.match(find_token(match_cfg, 'calc'), '%name%')
582+
assert cppcheckdata.match(find_token(match_cfg, 'int'), '%name%')
583+
assert not cppcheckdata.match(find_token(match_cfg, '3'), '%name%')
584+
assert not cppcheckdata.match(find_token(match_cfg, '('), '%name%')
585+
586+
def test_op(self, match_cfg):
587+
for op in ('|', '||', '*', '%', '!=', '>', '=', '+='):
588+
assert cppcheckdata.match(find_token(match_cfg, op), '%op%'), op
589+
assert not cppcheckdata.match(find_token(match_cfg, ';'), '%op%')
590+
assert not cppcheckdata.match(find_token(match_cfg, 'calc'), '%op%')
591+
592+
def test_or(self, match_cfg):
593+
assert cppcheckdata.match(find_token(match_cfg, '|'), '%or%')
594+
assert not cppcheckdata.match(find_token(match_cfg, '||'), '%or%')
595+
596+
def test_oror(self, match_cfg):
597+
assert cppcheckdata.match(find_token(match_cfg, '||'), '%oror%')
598+
assert not cppcheckdata.match(find_token(match_cfg, '|'), '%oror%')
599+
600+
def test_var(self, match_cfg):
601+
a_use = find_token(match_cfg, 'a', skip=1)
602+
assert cppcheckdata.match(a_use, '%var%')
603+
# a function name is a %name% but not a %var%
604+
assert not cppcheckdata.match(find_token(match_cfg, 'calc'), '%var%')
605+
assert not cppcheckdata.match(find_token(match_cfg, '3'), '%var%')
606+
607+
# ---- link patterns ----
608+
609+
def test_link_parentheses(self, match_cfg):
610+
calc_call = find_token(match_cfg, 'calc', skip=1)
611+
res = cppcheckdata.match(calc_call.next, '(*)')
612+
assert res
613+
assert res.end.str == ')'
614+
assert res.end is calc_call.next.link
615+
# the pattern continues after the linked token
616+
assert cppcheckdata.match(calc_call, '%name% (*) ;')
617+
# '(*)' only matches at a '(' token
618+
assert not cppcheckdata.match(calc_call, '(*)')
619+
620+
def test_link_brackets(self, match_cfg):
621+
arr_decl = find_token(match_cfg, 'arr')
622+
assert cppcheckdata.match(arr_decl, 'arr [*] ;')
623+
arr_use = find_token(match_cfg, 'arr', skip=1)
624+
res = cppcheckdata.match(arr_use, '%name% [*] %assign% %var% ;')
496625
assert res
497-
assert res.end.str == 'int'
498-
assert not cppcheckdata.match(add_def, 'sub (')
499-
assert not cppcheckdata.match(add_def, '')
500-
assert not cppcheckdata.match(None, 'add')
501-
502-
def test_match_patterns(self, sample_cfg):
503-
x_assign = find_token(sample_cfg, 'x', skip=1) # the 'x' in 'x = 42 ;'
504-
assert cppcheckdata.match(x_assign, '%name% %assign% %any% ;')
505-
assert cppcheckdata.match(x_assign, '%var% = 42')
506-
assert not cppcheckdata.match(x_assign, '%op%')
507-
plus = find_token(sample_cfg, '+')
508-
assert cppcheckdata.match(plus, '%op%')
509-
comparison = find_token(sample_cfg, '+')
510-
assert not cppcheckdata.match(comparison, '%comp%')
511626

512-
def test_match_bindings(self, sample_cfg):
513-
x_assign = find_token(sample_cfg, 'x', skip=1)
514-
res = cppcheckdata.match(x_assign, '%var%@lhs = %any%@rhs ;')
627+
def test_link_braces(self, match_cfg):
628+
if_scope = find_scope(match_cfg, 'If')
629+
res = cppcheckdata.match(if_scope.bodyStart, '{*}')
515630
assert res
516-
assert res.lhs.str == 'x'
517-
assert res.rhs.str == '42'
518-
# bindings are None on a failed match
519-
res = cppcheckdata.match(x_assign, '%var%@lhs + %any%@rhs')
520-
assert not res
521-
assert res.lhs is None
522-
assert res.rhs is None
631+
assert res.end is if_scope.bodyEnd
523632

524-
def test_match_alternatives(self, sample_cfg):
525-
x_assign = find_token(sample_cfg, 'x', skip=1)
526-
assert cppcheckdata.match(x_assign, 'y|x =')
527-
assert not cppcheckdata.match(x_assign, 'y|z =')
633+
def test_link_combined(self, match_cfg):
634+
if_tok = find_token(match_cfg, 'if')
635+
res = cppcheckdata.match(if_tok, 'if (*) {*}')
636+
assert res
637+
assert res.end.str == '}'
638+
639+
def test_link_angle_brackets(self, match_cpp_cfg):
640+
# the '<' of 'std::vector<int>' is linked to the '>'
641+
template_lt = find_token(match_cpp_cfg, '<')
642+
assert template_lt.link
643+
res = cppcheckdata.match(template_lt, '<*>')
644+
assert res
645+
assert res.end.str == '>'
646+
assert cppcheckdata.match(template_lt.previous, 'vector <*> %var%')
647+
# the '<' in 'i < j' is a comparison without a link
648+
comparison_lt = find_token(match_cpp_cfg, '<', skip=1)
649+
assert comparison_lt.link is None
650+
assert not cppcheckdata.match(comparison_lt, '<*>')
528651

529-
def test_match_negation(self, sample_cfg):
530-
x_assign = find_token(sample_cfg, 'x', skip=1)
531-
assert cppcheckdata.match(x_assign, 'x !!+')
532-
assert not cppcheckdata.match(x_assign, 'x !!=')
652+
# ---- either-or alternation ----
533653

534-
def test_match_link(self, sample_cfg):
535-
add_def = find_token(sample_cfg, 'add')
536-
res = cppcheckdata.match(add_def.next, '(*)')
654+
def test_alternatives(self, match_cfg):
655+
struct_tok = find_token(match_cfg, 'struct')
656+
assert cppcheckdata.match(struct_tok, 'struct|class %name% {')
657+
assert not cppcheckdata.match(struct_tok, 'union|enum %name% {')
658+
659+
def test_alternatives_cpp(self, match_cpp_cfg):
660+
class_tok = find_token(match_cpp_cfg, 'class')
661+
assert cppcheckdata.match(class_tok, 'struct|class %name% {')
662+
663+
def test_alternatives_with_keyword(self, match_cfg):
664+
struct_tok = find_token(match_cfg, 'struct')
665+
assert cppcheckdata.match(struct_tok, '%op%|struct')
666+
assert not cppcheckdata.match(struct_tok, '%op%|%comp%')
667+
668+
# ---- negation ----
669+
670+
def test_negation(self, match_cfg):
671+
struct_tok = find_token(match_cfg, 'struct')
672+
assert cppcheckdata.match(struct_tok, 'struct !!;')
673+
assert not cppcheckdata.match(struct_tok, 'struct !!Point')
674+
675+
def test_negation_keyword(self, match_cfg):
676+
calc_call = find_token(match_cfg, 'calc', skip=1)
677+
assert cppcheckdata.match(calc_call, '%name% !!%op%')
678+
assert not cppcheckdata.match(calc_call, '%name% !!(')
679+
680+
@pytest.mark.xfail(strict=False,
681+
reason='in the C++ Token::Match a negation also matches when there is no token, '
682+
'in match() it does not')
683+
def test_negation_no_token(self, match_cfg):
684+
last = match_cfg.tokenlist[-1]
685+
assert last.str == '}'
686+
assert cppcheckdata.match(last, '} !!x')
687+
688+
# ---- bindings ----
689+
690+
def test_bindings(self, match_cfg):
691+
calc_call = find_token(match_cfg, 'calc', skip=1)
692+
res = cppcheckdata.match(calc_call, '%name%@ftok (*)')
537693
assert res
694+
assert res.ftok is calc_call
695+
assert res.ftok.str == 'calc'
538696
assert res.end.str == ')'
539697

698+
def test_multiple_bindings(self, match_cfg):
699+
bit_or_lhs = find_token(match_cfg, 'bit_or', skip=1)
700+
res = cppcheckdata.match(bit_or_lhs, '%var%@lhs = %var%@op1 | %var%@op2 ;')
701+
assert res
702+
assert res.lhs.str == 'bit_or'
703+
assert res.op1.str == 'a'
704+
assert res.op2.str == 'b'
705+
706+
def test_binding_on_link_pattern(self, match_cfg):
707+
calc_call = find_token(match_cfg, 'calc', skip=1)
708+
# the binding is the '(' token, end is behind the linked ')'
709+
res = cppcheckdata.match(calc_call, '%name% (*)@paren')
710+
assert res
711+
assert res.paren.str == '('
712+
assert res.paren.link is res.end
713+
714+
def test_bindings_on_failure(self, match_cfg):
715+
calc_call = find_token(match_cfg, 'calc', skip=1)
716+
res = cppcheckdata.match(calc_call, '%name%@ftok [*]@brackets')
717+
assert not res
718+
# all bindings (including 'end') read as None on a failed match
719+
assert res.ftok is None
720+
assert res.brackets is None
721+
assert res.end is None
722+
723+
def test_unknown_binding_raises(self, match_cfg):
724+
calc_call = find_token(match_cfg, 'calc', skip=1)
725+
res = cppcheckdata.match(calc_call, '%name%@ftok (*)')
726+
assert res
727+
with pytest.raises(AttributeError):
728+
res.nosuchbinding
729+
res = cppcheckdata.match(calc_call, '%name%@ftok [*]')
730+
assert not res
731+
with pytest.raises(AttributeError):
732+
res.nosuchbinding
733+
734+
# ---- '**' forward search ----
735+
736+
def test_find_forward(self, match_cfg):
737+
calc_def = find_token(match_cfg, 'calc')
738+
# '**' searches forward, skipping linked groups: the '{' found here
739+
# is the function body, not a '{' inside the parameter list
740+
res = cppcheckdata.match(calc_def, 'calc **{')
741+
assert res
742+
assert res.end.str == '{'
743+
assert not cppcheckdata.match(calc_def, 'calc **nosuchtoken')
744+
540745

541746
class TestSuppressions:
542747
def make_suppression(self, **kwargs):

0 commit comments

Comments
 (0)