Skip to content

Commit 4521921

Browse files
committed
Update tests and documentation #56
* Return license validation results in ExpressionInfo object Signed-off-by: Jono Yang <[email protected]>
1 parent 97d1fe6 commit 4521921

File tree

2 files changed

+102
-16
lines changed

2 files changed

+102
-16
lines changed

src/license_expression/__init__.py

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -603,46 +603,73 @@ def simple_tokenizer(self, expression):
603603
sym = LicenseSymbol(key=sym_or_op)
604604
yield Token(start, end, sym_or_op, sym)
605605

606-
def validate(self, expression, strict=False, **kwargs):
607-
data = {
608-
'normalized_license_expression': '',
609-
'errors': [],
610-
'valid_symbols': [],
611-
'invalid_symbols': [],
612-
'exception_symbols': [],
613-
}
606+
def validate(self, expression, strict=True, **kwargs):
607+
"""
608+
Return a ExpressionInfo object that contains information about
609+
`expression` by parsing `expression` using Licensing.parse()
610+
611+
The ExpressionInfo class has the following fields:
612+
- normalized_license_expression: str
613+
- errors: list
614+
- valid_symbols: list
615+
- invalid_symbols: list
616+
- exception_symbols: list
617+
618+
If `expression` is valid, then
619+
`ExpressionInfo.normalized_license_expression` is set, along with a list
620+
of valid license symbols in `valid_symbols` and `exception_symbols`.
621+
622+
If an error was encountered when validating `expression`,
623+
`ExpressionInfo.errors` will be populated with strings containing the
624+
error message that has occured. If an error has occured due to invalid
625+
license symbols, the offending symbols will be present in
626+
`ExpressionInfo.invalid_symbols`
627+
628+
If `strict` is True, additional exceptions will be raised if in a "WITH"
629+
expression such as "XXX with ZZZ" if the XXX symbol has `is_exception`
630+
set to True or the YYY symbol has `is_exception` set to False. This
631+
checks that symbols are used strictly as constructed.
632+
"""
633+
class ExpressionInfo:
634+
normalized_license_expression = ''
635+
errors = []
636+
valid_symbols = []
637+
invalid_symbols = []
638+
exception_symbols = []
639+
640+
data = ExpressionInfo()
614641

615642
# Check `expression` type
616643
try:
617644
self.parse(expression)
618645
except ExpressionError as e:
619-
data['errors'].append(str(e))
646+
data.errors.append(str(e))
620647
return data
621648

622649
# Check `expression` syntax
623650
try:
624651
self.parse(expression, strict=strict)
625652
except ExpressionParseError as e:
626-
data['errors'].append(str(e))
627-
data['invalid_symbols'].append(e.token_string)
653+
data.errors.append(str(e))
654+
data.invalid_symbols.append(e.token_string)
628655
return data
629656

630657
# Check `expression` keys
631658
try:
632659
parsed_expression = self.parse(expression, strict=strict, validate=True)
633660
except ExpressionError as e:
634661
error_message = str(e)
635-
data['errors'].append(error_message)
662+
data.errors.append(error_message)
636663
if 'Unknown license key' in error_message:
637664
unknown_keys = self.unknown_license_keys(expression)
638-
data['invalid_symbols'].extend(unknown_keys)
665+
data.invalid_symbols.extend(unknown_keys)
639666
return data
640667

641668
# If we have not hit an exception, load `data` and return it
642669
symbols = list(parsed_expression.symbols)
643-
data['normalized_license_expression'] = parsed_expression.render()
644-
data['valid_symbols'] = [s.render() for s in symbols]
645-
data['exception_symbols'] = [s.render() for s in symbols if isinstance(s, LicenseWithExceptionSymbol) or s.is_exception]
670+
data.normalized_license_expression = parsed_expression.render()
671+
data.valid_symbols = [s.render() for s in symbols]
672+
data.exception_symbols = [s.render() for s in symbols if isinstance(s, LicenseWithExceptionSymbol) or s.is_exception]
646673
return data
647674

648675

tests/test_license_expression.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2165,3 +2165,62 @@ def test_tokenize_or_or(self):
21652165
]
21662166

21672167
assert expected == results
2168+
2169+
2170+
class LicensingValidateTest(TestCase):
2171+
licensing = Licensing(
2172+
[
2173+
LicenseSymbol(key='GPL-2.0-or-later', is_exception=False),
2174+
LicenseSymbol(key='MIT', is_exception=False),
2175+
LicenseSymbol(key='Apache-2.0', is_exception=False),
2176+
LicenseSymbol(key='WxWindows-exception-3.1', is_exception=True),
2177+
]
2178+
)
2179+
2180+
def test_validate_simple(self):
2181+
result = self.licensing.validate('GPL-2.0-or-later AND MIT')
2182+
assert 'GPL-2.0-or-later AND MIT' == result.normalized_license_expression
2183+
assert [] == result.errors
2184+
assert sorted(['MIT', 'GPL-2.0-or-later']) == sorted(result.valid_symbols)
2185+
assert [] == result.invalid_symbols
2186+
assert [] == result.exception_symbols
2187+
2188+
def test_validation_invalid_license_key(self):
2189+
result = self.licensing.validate('cool-license')
2190+
assert '' == result.normalized_license_expression
2191+
assert ['Unknown license key(s): cool-license'] == result.errors
2192+
assert [] == result.valid_symbols
2193+
assert ['cool-license'] == result.invalid_symbols
2194+
assert [] == result.exception_symbols
2195+
2196+
def test_validate_exception(self):
2197+
result = self.licensing.validate('GPL-2.0-or-later WITH WxWindows-exception-3.1')
2198+
assert 'GPL-2.0-or-later WITH WxWindows-exception-3.1' == result.normalized_license_expression
2199+
assert [] == result.errors
2200+
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.valid_symbols
2201+
assert [] == result.invalid_symbols
2202+
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.exception_symbols
2203+
2204+
def test_validation_exception_with_choice(self):
2205+
result = self.licensing.validate('GPL-2.0-or-later WITH WxWindows-exception-3.1 OR MIT')
2206+
assert 'GPL-2.0-or-later WITH WxWindows-exception-3.1 OR MIT' == result.normalized_license_expression
2207+
assert [] == result.errors
2208+
assert sorted(['GPL-2.0-or-later WITH WxWindows-exception-3.1', 'MIT']) == sorted(result.valid_symbols)
2209+
assert [] == result.invalid_symbols
2210+
assert ['GPL-2.0-or-later WITH WxWindows-exception-3.1'] == result.exception_symbols
2211+
2212+
def test_validation_bad_syntax(self):
2213+
result = self.licensing.validate('Apache-2.0 + MIT')
2214+
assert '' == result.normalized_license_expression
2215+
assert ['Invalid symbols sequence such as (A B) for token: "+" at position: 11'] == result.errors
2216+
assert [] == result.valid_symbols
2217+
assert [] == result.invalid_symbols
2218+
assert [] == result.exception_symbols
2219+
2220+
def test_validation_invalid_license_exception(self):
2221+
result = self.licensing.validate('Apache-2.0 WITH MIT')
2222+
assert '' == result.normalized_license_expression
2223+
assert ["A plain license symbol cannot be used as an exception in a \"WITH symbol\" statement. for token: \"MIT\" at position: 16"] == result.errors
2224+
assert [] == result.valid_symbols
2225+
assert ['MIT'] == result.invalid_symbols
2226+
assert [] == result.exception_symbols

0 commit comments

Comments
 (0)