diff --git a/spdxexp/benchmark_satisfies_test.go b/spdxexp/benchmark_satisfies_test.go new file mode 100644 index 0000000..646884e --- /dev/null +++ b/spdxexp/benchmark_satisfies_test.go @@ -0,0 +1,493 @@ +package spdxexp + +import ( + "fmt" + "testing" +) + +type satisfiesBenchmarkScenario struct { + name string + testExpression string +} + +var satisfiesBenchmarkScenarios = []satisfiesBenchmarkScenario{ + // Scenario order is used as-is in the summary table. + {"MIT", "MIT"}, + {"mit", "mit"}, + {"Apache-2.0", "Apache-2.0"}, + {"Zed", "Zed"}, + {"MIT AND Apache-2.0", "MIT AND Apache-2.0"}, + {"MIT AND Apache-2.0 OR Zed", "MIT AND Apache-2.0 OR Zed"}, + {"GPL-2.0-or-later", "GPL-2.0-or-later"}, + {"GPL-2.0+", "GPL-2.0+"}, +} + +func BenchmarkSatisfies(b *testing.B) { + for _, scenario := range satisfiesBenchmarkScenarios { + scenario := scenario + b.Run(scenario.name, func(b *testing.B) { + benchmarkSatisfiesScenario(b, scenario.testExpression) + }) + } +} + +func benchmarkSatisfiesScenario(b *testing.B, expression string) { + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _, err := Satisfies(expression, allowList()) + if err != nil { + b.Fatalf("Satisfies(%q, allowList) error: %v", expression, err) + } + } +} + +func computeSatisfiesBenchmarkTableRows(repeats int) []benchmarkTableRow { + rows := make([]benchmarkTableRow, 0, len(satisfiesBenchmarkScenarios)) + + for _, scenario := range satisfiesBenchmarkScenarios { + scenario := scenario + avg := runBenchmarkNsAvg(repeats, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := Satisfies(scenario.testExpression, allowList()) + if err != nil { + panic(fmt.Sprintf("Satisfies scenario %q error: %v", scenario.name, err)) + } + } + }) + rows = append(rows, benchmarkTableRow{label: scenario.name, nsOpAvg: avg}) + } + + return rows +} + +func allowList() []string { + // common list of permissive licenses to simulate a realistic use case for Satisfies + return []string{ + "0BSD", + "3D-Slicer-1.0", + "AAL", + "Abstyles", + "AdaCore-doc", + "Adobe-2006", + "Adobe-Display-PostScript", + "Adobe-Glyph", + "Adobe-Utopia", + "ADSL", + "AFL-1.1", + "AFL-1.2", + "AFL-2.0", + "AFL-2.1", + "AFL-3.0", + "Afmparse", + "AMD-newlib", + "AMDPLPA", + "AML", + "AML-glslang", + "AMPAS", + "ANTLR-PD", + "ANTLR-PD-fallback", + "Apache-1.0", + "Apache-1.1", + "Apache-2.0", + "APAFML", + "App-s2p", + "Aspell-RU", + "Baekmuk", + "Bahyph", + "Barr", + "bcrypt-Solar-Designer", + "Beerware", + "Bitstream-Charter", + "Bitstream-Vera", + "blessing", + "BlueOak-1.0.0", + "Boehm-GC", + "Boehm-GC-without-fee", + "Borceux", + "Brian-Gladman-2-Clause", + "Brian-Gladman-3-Clause", + "BSD-1-Clause", + "BSD-2-Clause", + "BSD-2-Clause-Darwin", + "BSD-2-Clause-first-lines", + "BSD-2-Clause-Patent", + "BSD-2-Clause-pkgconf-disclaimer", + "BSD-2-Clause-Views", + "BSD-3-Clause", + "BSD-3-Clause-acpica", + "BSD-3-Clause-Attribution", + "BSD-3-Clause-Clear", + "BSD-3-Clause-flex", + "BSD-3-Clause-HP", + "BSD-3-Clause-LBNL", + "BSD-3-Clause-Modification", + "BSD-3-Clause-Open-MPI", + "BSD-3-Clause-Sun", + "BSD-4-Clause", + "BSD-4-Clause-Shortened", + "BSD-4-Clause-UC", + "BSD-4.3RENO", + "BSD-4.3TAHOE", + "BSD-Advertising-Acknowledgement", + "BSD-Attribution-HPND-disclaimer", + "BSD-Inferno-Nettverk", + "BSD-Source-beginning-file", + "BSD-Source-Code", + "BSD-Systemics", + "BSD-Systemics-W3Works", + "BSL-1.0", + "bzip2-1.0.6", + "Caldera-no-preamble", + "Catharon", + "CC-BY-1.0", + "CC-BY-2.0", + "CC-BY-2.5", + "CC-BY-2.5-AU", + "CC-BY-3.0", + "CC-BY-3.0-AT", + "CC-BY-3.0-AU", + "CC-BY-3.0-DE", + "CC-BY-3.0-IGO", + "CC-BY-3.0-NL", + "CC-BY-3.0-US", + "CC-BY-4.0", + "CC-PDDC", + "CC-PDM-1.0", + "CC0-1.0", + "CDLA-Permissive-1.0", + "CDLA-Permissive-2.0", + "CECILL-B", + "CERN-OHL-1.1", + "CERN-OHL-1.2", + "CERN-OHL-P-2.0", + "CFITSIO", + "check-cvs", + "checkmk", + "Clips", + "CMU-Mach", + "CMU-Mach-nodoc", + "CNRI-Jython", + "CNRI-Python", + "CNRI-Python-GPL-Compatible", + "COIL-1.0", + "Community-Spec-1.0", + "Condor-1.1", + "Cornell-Lossless-JPEG", + "Cronyx", + "Crossword", + "CryptoSwift", + "CrystalStacker", + "Cube", + "curl", + "cve-tou", + "DEC-3-Clause", + "diffmark", + "DL-DE-BY-2.0", + "DL-DE-ZERO-2.0", + "DOC", + "DocBook-DTD", + "DocBook-Schema", + "DocBook-Stylesheet", + "DocBook-XML", + "Dotseqn", + "DRL-1.0", + "DRL-1.1", + "DSDP", + "dtoa", + "dvipdfm", + "ECL-1.0", + "ECL-2.0", + "EFL-1.0", + "EFL-2.0", + "eGenix", + "Entessa", + "EPICS", + "etalab-2.0", + "EUDatagrid", + "Fair", + "FBM", + "Ferguson-Twofish", + "FreeBSD-DOC", + "FSFAP", + "FSFAP-no-warranty-disclaimer", + "FSFUL", + "FSFULLR", + "FSFULLRSD", + "FSFULLRWD", + "FTL", + "Furuseth", + "fwlw", + "Game-Programming-Gems", + "GD", + "generic-xts", + "Giftware", + "Glulxe", + "GLWTPL", + "Graphics-Gems", + "gtkbook", + "Gutmann", + "HaskellReport", + "HDF5", + "hdparm", + "HIDAPI", + "HP-1986", + "HP-1989", + "HPND", + "HPND-DEC", + "HPND-doc", + "HPND-doc-sell", + "HPND-export-US-modify", + "HPND-export2-US", + "HPND-Fenneberg-Livingston", + "HPND-INRIA-IMAG", + "HPND-Intel", + "HPND-Kevlin-Henney", + "HPND-Markus-Kuhn", + "HPND-merchantability-variant", + "HPND-MIT-disclaimer", + "HPND-Netrek", + "HPND-Pbmplus", + "HPND-sell-MIT-disclaimer-xserver", + "HPND-sell-regexpr", + "HPND-sell-variant", + "HPND-sell-variant-MIT-disclaimer", + "HPND-sell-variant-MIT-disclaimer-rev", + "HPND-UC", + "HTMLTIDY", + "IBM-pibs", + "ICU", + "IEC-Code-Components-EULA", + "IJG", + "IJG-short", + "ImageMagick", + "iMatix", + "Info-ZIP", + "Inner-Net-2.0", + "InnoSetup", + "Intel", + "Intel-ACPI", + "ISC", + "ISC-Veillard", + "Jam", + "JasPer-2.0", + "jove", + "JPNIC", + "JSON", + "Kastrup", + "Kazlib", + "Knuth-CTAN", + "Latex2e", + "Latex2e-translated-notice", + "Leptonica", + "Libpng", + "libpng-1.6.35", + "libpng-2.0", + "libselinux-1.0", + "libtiff", + "libutil-David-Nugent", + "Linux-OpenIB", + "LOOP", + "LPD-document", + "lsof", + "Lucida-Bitmap-Fonts", + "LZMA-SDK-9.11-to-9.20", + "LZMA-SDK-9.22", + "Mackerras-3-Clause", + "Mackerras-3-Clause-acknowledgment", + "magaz", + "mailprio", + "man2html", + "Martin-Birgmeier", + "McPhee-slideshow", + "metamail", + "Minpack", + "MIPS", + "MirOS", + "MIT", + "MIT-0", + "MIT-advertising", + "MIT-Click", + "MIT-CMU", + "MIT-enna", + "MIT-feh", + "MIT-Festival", + "MIT-Khronos-old", + "MIT-Modern-Variant", + "MIT-open-group", + "MIT-testregex", + "MIT-Wu", + "MITNFA", + "MMIXware", + "MPEG-SSG", + "mpi-permissive", + "mpich2", + "mplus", + "MS-LPL", + "MS-PL", + "MTLL", + "MulanPSL-1.0", + "MulanPSL-2.0", + "Multics", + "Mup", + "NAIST-2003", + "Naumen", + "NCBI-PD", + "NCL", + "NCSA", + "NetCDF", + "Newsletr", + "ngrep", + "NICTA-1.0", + "NIST-PD", + "NIST-PD-fallback", + "NIST-Software", + "NLOD-1.0", + "NLOD-2.0", + "NLPL", + "NRL", + "NTIA-PD", + "NTP", + "NTP-0", + "O-UDA-1.0", + "OAR", + "ODC-By-1.0", + "OFFIS", + "OFL-1.0", + "OFL-1.0-no-RFN", + "OFL-1.0-RFN", + "OFL-1.1-no-RFN", + "OFL-1.1-RFN", + "OGC-1.0", + "OGDL-Taiwan-1.0", + "OGL-Canada-2.0", + "OGL-UK-1.0", + "OGL-UK-2.0", + "OGL-UK-3.0", + "OLDAP-2.0", + "OLDAP-2.0.1", + "OLDAP-2.1", + "OLDAP-2.2", + "OLDAP-2.2.1", + "OLDAP-2.2.2", + "OLDAP-2.3", + "OLDAP-2.4", + "OLDAP-2.5", + "OLDAP-2.6", + "OLDAP-2.7", + "OLDAP-2.8", + "OLFL-1.3", + "OML", + "OpenSSL", + "OpenSSL-standalone", + "OpenVision", + "OPL-UK-3.0", + "OPUBL-1.0", + "PADL", + "PDDL-1.0", + "PHP-3.0", + "PHP-3.01", + "Pixar", + "pkgconf", + "Plexus", + "pnmstitch", + "PostgreSQL", + "PSF-2.0", + "psfrag", + "psutils", + "Python-2.0", + "Python-2.0.1", + "python-ldap", + "radvd", + "Rdisc", + "RSA-MD", + "Ruby-pty", + "SAX-PD", + "SAX-PD-2.0", + "Saxpath", + "SCEA", + "SchemeReport", + "Sendmail", + "Sendmail-Open-Source-1.1", + "SGI-B-1.1", + "SGI-B-2.0", + "SGI-OpenGL", + "SGP4", + "SHL-0.5", + "SHL-0.51", + "SL", + "SMLNJ", + "snprintf", + "softSurfer", + "Soundex", + "Spencer-86", + "Spencer-94", + "Spencer-99", + "ssh-keyscan", + "SSH-OpenSSH", + "SSH-short", + "SSLeay-standalone", + "Sun-PPP", + "Sun-PPP-2000", + "SunPro", + "SWL", + "swrule", + "Symlinks", + "TCL", + "TCP-wrappers", + "TermReadKey", + "ThirdEye", + "threeparttable", + "TPDL", + "TrustedQSL", + "TTWL", + "TTYP0", + "TU-Berlin-1.0", + "TU-Berlin-2.0", + "UCAR", + "ulem", + "UMich-Merit", + "Unicode-3.0", + "Unicode-DFS-2015", + "Unicode-DFS-2016", + "UnixCrypt", + "Unlicense", + "Unlicense-libtelnet", + "Unlicense-libwhirlpool", + "UPL-1.0", + "VSL-1.0", + "W3C", + "W3C-19980720", + "W3C-20150513", + "w3m", + "Widget-Workshop", + "Wsuipa", + "WTFPL", + "wwl", + "X11", + "X11-distribute-modifications-variant", + "X11-swapped", + "Xdebug-1.03", + "Xerox", + "Xfig", + "XFree86-1.1", + "xinetd", + "xkeyboard-config-Zinoviev", + "xlock", + "Xnet", + "xpp", + "XSkat", + "xzoom", + "Zed", + "Zeeff", + "Zend-2.0", + "Zlib", + "zlib-acknowledgement", + "ZPL-1.1", + "ZPL-2.0", + "ZPL-2.1", + } +} diff --git a/spdxexp/benchmark_setup_test.go b/spdxexp/benchmark_setup_test.go new file mode 100644 index 0000000..85bab37 --- /dev/null +++ b/spdxexp/benchmark_setup_test.go @@ -0,0 +1,195 @@ +package spdxexp + +import ( + "flag" + "fmt" + "math" + "os" + "strings" + "testing" + "time" +) + +const benchmarkRepeatsForSummary = 4 + +// Fixed baselines for the Scale column in the summary tables. +// Using constants makes the scale values comparable across runs/branches. +const ( + benchmarkScaleBaselineValidateLicensesNsOp = 5.0 + benchmarkScaleBaselineSatisfiesNsOp = 1500.0 +) + +func TestMain(m *testing.M) { + // When TestMain is present, it's safest to explicitly parse flags before + // inspecting any -test.* settings. + if !flag.Parsed() { + flag.Parse() + } + + benchPattern := "" + benchFlag := flag.Lookup("test.bench") + if benchFlag != nil { + benchPattern = benchFlag.Value.String() + } + + shouldPrintBenchOutput := benchPattern != "" + if shouldPrintBenchOutput { + // Benchmarks are executed as part of summary table generation (via + // testing.Benchmark). Suppress the default go test benchmark execution so + // we don't run benchmarks twice in a single invocation. + if benchFlag != nil { + _ = benchFlag.Value.Set("$^") + } + + fmt.Fprintln(os.Stdout, "Benchmark summary tables:") + fmt.Fprintln(os.Stdout, "- ns/op average: average time per operation") + fmt.Fprintln(os.Stdout, "- Scale: relative to a fixed baseline per table") + fmt.Fprintln(os.Stdout, "") + } + + code := m.Run() + + if shouldPrintBenchOutput { + validateRows := withScaleColumn(computeValidateLicensesBenchmarkTableRows(benchmarkRepeatsForSummary), benchmarkScaleBaselineValidateLicensesNsOp) + printBenchmarkTable(os.Stdout, "Benchmark ValidateLicenses", validateRows, benchmarkScaleBaselineValidateLicensesNsOp) + + satisfiesRows := withScaleColumn(computeSatisfiesBenchmarkTableRows(benchmarkRepeatsForSummary), benchmarkScaleBaselineSatisfiesNsOp) + printBenchmarkTable(os.Stdout, "Benchmark Satisfies", satisfiesRows, benchmarkScaleBaselineSatisfiesNsOp) + } + + os.Exit(code) +} + +type benchmarkTableRow struct { + label string + nsOpAvg float64 + scale string +} + +func withScaleColumn(rows []benchmarkTableRow, benchmarkScaleBaselineNsOp float64) []benchmarkTableRow { + if len(rows) == 0 { + return rows + } + + for i := range rows { + rows[i].scale = formatScale(rows[i].nsOpAvg, benchmarkScaleBaselineNsOp) + } + return rows +} + +func formatScale(ns, baseline float64) string { + if ns <= 0 || baseline <= 0 { + return "n/a" + } + + ratio := ns / baseline + if ratio >= 0.95 && ratio <= 1.05 { + return "1x" + } + + if ratio >= 10 { + return fmt.Sprintf("~%sx", formatWithCommas(int64(math.Round(ratio)))) + } + + return fmt.Sprintf("~%.1fx", math.Round(ratio*10)/10) +} + +func runBenchmarkNsAvg(repeats int, fn func(b *testing.B)) float64 { + if repeats <= 0 { + repeats = 1 + } + + sum := 0.0 + count := 0 + for i := 0; i < repeats; i++ { + res := testing.Benchmark(fn) + if res.N <= 0 { + continue + } + ns := float64(res.T.Nanoseconds()) / float64(res.N) + sum += ns + count++ + } + if count == 0 { + return 0 + } + return sum / float64(count) +} + +func printBenchmarkTable(w *os.File, title string, rows []benchmarkTableRow, benchmarkScaleBaselineNsOp float64) { + header1 := title + header2 := "ns/op average" + header3 := fmt.Sprintf("Scale (%dns/op=1x)", int(benchmarkScaleBaselineNsOp)) + + col1 := len(header1) + for _, r := range rows { + if len(r.label) > col1 { + col1 = len(r.label) + } + } + + formatNsAvg := func(r benchmarkTableRow) string { + num := nsNumberString(r.nsOpAvg) + return fmt.Sprintf("~%s ns/op", num) + } + + col2 := len(header2) + for _, r := range rows { + if l := len(formatNsAvg(r)); l > col2 { + col2 = l + } + } + + col3 := len(header3) + for _, r := range rows { + if len(r.scale) > col3 { + col3 = len(r.scale) + } + } + + line := func() { + fmt.Fprintf(w, "+-%s-+-%s-+-%s-+\n", strings.Repeat("-", col1), strings.Repeat("-", col2), strings.Repeat("-", col3)) + } + row := func(c1, c2, c3 string) { + fmt.Fprintf(w, "| %-*s | %-*s | %-*s |\n", col1, c1, col2, c2, col3, c3) + } + + line() + row(header1, header2, header3) + line() + for _, r := range rows { + ns := formatNsAvg(r) + row(r.label, ns, r.scale) + } + line() + fmt.Fprintln(w, "") +} + +func nsNumberString(ns float64) string { + if ns <= 0 { + return "0" + } + if ns < float64(10*time.Microsecond.Nanoseconds()) { + return fmt.Sprintf("%.1f", ns) + } + return formatWithCommas(int64(ns + 0.5)) +} + +func formatWithCommas(n int64) string { + s := fmt.Sprintf("%d", n) + if len(s) <= 3 { + return s + } + + var b strings.Builder + pre := len(s) % 3 + if pre == 0 { + pre = 3 + } + b.WriteString(s[:pre]) + for i := pre; i < len(s); i += 3 { + b.WriteByte(',') + b.WriteString(s[i : i+3]) + } + return b.String() +} diff --git a/spdxexp/benchmark_validate_licenses_test.go b/spdxexp/benchmark_validate_licenses_test.go new file mode 100644 index 0000000..0f2124e --- /dev/null +++ b/spdxexp/benchmark_validate_licenses_test.go @@ -0,0 +1,65 @@ +package spdxexp + +import ( + "fmt" + "testing" +) + +type validateLicensesBenchmarkScenario struct { + name string + testLicenses []string +} + +var validateLicensesBenchmarkScenarios = []validateLicensesBenchmarkScenario{ + // Scenario order is used as-is in the summary table. + {"MIT", []string{"MIT"}}, + {"mit", []string{"mit"}}, + {"Apache-2.0", []string{"Apache-2.0"}}, + {"Zed", []string{"Zed"}}, + {"MIT AND Apache-2.0", []string{"MIT", "Apache-2.0"}}, + {"MIT AND Apache-2.0 OR Zed", []string{"MIT", "Apache-2.0", "Zed"}}, + {"GPL-2.0-or-later", []string{"GPL-2.0-or-later"}}, + {"GPL-2.0+", []string{"GPL-2.0+"}}, +} + +func BenchmarkValidateLicenses(b *testing.B) { + for _, scenario := range validateLicensesBenchmarkScenarios { + scenario := scenario + b.Run(scenario.name, func(b *testing.B) { + benchmarkValidateLicensesScenario(b, scenario.testLicenses) + }) + } +} + +func benchmarkValidateLicensesScenario(b *testing.B, licenses []string) { + b.ReportAllocs() + b.ResetTimer() + + for i := 0; i < b.N; i++ { + valid, invalidLicenses := ValidateLicenses(licenses) + if !valid || len(invalidLicenses) != 0 { + b.Fatalf("ValidateLicenses(%v) returned valid=%v invalid=%v", licenses, valid, invalidLicenses) + } + } +} + +func computeValidateLicensesBenchmarkTableRows(repeats int) []benchmarkTableRow { + rows := make([]benchmarkTableRow, 0, len(validateLicensesBenchmarkScenarios)) + + for _, scenario := range validateLicensesBenchmarkScenarios { + scenario := scenario + avg := runBenchmarkNsAvg(repeats, func(b *testing.B) { + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + valid, invalidLicenses := ValidateLicenses(scenario.testLicenses) + if !valid || len(invalidLicenses) != 0 { + panic(fmt.Sprintf("ValidateLicenses scenario %q failed: valid=%v invalid=%v", scenario.name, valid, invalidLicenses)) + } + } + }) + rows = append(rows, benchmarkTableRow{label: scenario.name, nsOpAvg: avg}) + } + + return rows +} diff --git a/spdxexp/license.go b/spdxexp/license.go index b47ef45..a1800b0 100644 --- a/spdxexp/license.go +++ b/spdxexp/license.go @@ -11,6 +11,11 @@ func activeLicense(id string) (bool, string) { return inLicenseList(spdxlicenses.GetLicenses(), id) } +// ActiveLicense returns true if the id is an active license. +func ActiveLicense(id string) (bool, string) { + return activeLicense(id) +} + // deprecatedLicense returns true if the id is a deprecated license. func deprecatedLicense(id string) (bool, string) { return inLicenseList(spdxlicenses.GetDeprecated(), id) diff --git a/spdxexp/satisfies.go b/spdxexp/satisfies.go index abd4778..f2bc9d6 100644 --- a/spdxexp/satisfies.go +++ b/spdxexp/satisfies.go @@ -3,12 +3,26 @@ package spdxexp import ( "errors" "sort" + "strings" ) // ValidateLicenses checks if given licenses are valid according to spdx. // Returns true if all licenses are valid; otherwise, false. // Returns all the invalid licenses contained in the `licenses` argument. func ValidateLicenses(licenses []string) (bool, []string) { + // simple check for MIT covers the most common case and avoids the overhead of parsing for valid licenses + if len(licenses) == 1 && strings.EqualFold(licenses[0], "MIT") { + return true, []string{} + } + + // if only one license, check for active license first since that is the next most common case + if len(licenses) == 1 { + if ok, _ := ActiveLicense(licenses[0]); ok { + return true, []string{} + } + } + + // handle all other cases with parsing, which will cover both single and multiple licenses and expressions valid := true invalidLicenses := []string{} for _, license := range licenses { @@ -24,13 +38,36 @@ func ValidateLicenses(licenses []string) (bool, []string) { // Returns true if allowed list satisfies test license expression; otherwise, false. // Returns error if error occurs during processing. func Satisfies(testExpression string, allowedList []string) (bool, error) { + if len(allowedList) == 0 { + return false, errors.New("allowedList requires at least one element, but is empty") + } + + // simple check for MIT covers the most common case and avoids the overhead of parsing the testExpression + if strings.EqualFold(testExpression, "MIT") { + for _, allowed := range allowedList { + if strings.EqualFold(allowed, "MIT") { + return true, nil + } + } + return false, nil + } + + // if only one license in the test expression, check for active license first to avoid the overhead of parsing + if !strings.Contains(testExpression, " ") { + if ok, _ := ActiveLicense(testExpression); ok { + for _, allowed := range allowedList { + if strings.EqualFold(allowed, testExpression) { + return true, nil + } + } + } + } + + // handle all other cases with parsing, which will cover both single and multiple licenses and expressions expressionNode, err := parse(testExpression) if err != nil { return false, err } - if len(allowedList) == 0 { - return false, errors.New("allowedList requires at least one element, but is empty") - } allowedNodes, err := stringsToNodes(allowedList) if err != nil { return false, err diff --git a/spdxexp/satisfies_test.go b/spdxexp/satisfies_test.go index 0a240d0..e07fa80 100644 --- a/spdxexp/satisfies_test.go +++ b/spdxexp/satisfies_test.go @@ -15,6 +15,9 @@ func TestValidateLicenses(t *testing.T) { allValid bool invalidLicenses []string }{ + {"MIT shortcut test", []string{"MIT"}, true, []string{}}, + {"mit shortcut test", []string{"mit"}, true, []string{}}, + {"Apache-2.0 active shortcut test", []string{"Apache-2.0"}, true, []string{}}, {"All invalid", []string{"MTI", "Apche-2.0", "0xDEADBEEF", ""}, false, []string{"MTI", "Apche-2.0", "0xDEADBEEF", ""}}, {"All valid", []string{"MIT", "Apache-2.0", "GPL-2.0"}, true, []string{}}, {"Some invalid", []string{"MTI", "Apche-2.0", "GPL-2.0"}, false, []string{"MTI", "Apche-2.0"}}, @@ -26,6 +29,7 @@ func TestValidateLicenses(t *testing.T) { "LGPL-2.1-only OR MIT OR BSD-3-Clause", "GPL-2.0-or-later WITH Bison-exception-2.2", }, false, []string{"MIT AND APCHE-2.0"}}, + {"Empty string is invalid", []string{""}, false, []string{""}}, } for _, test := range tests { @@ -73,7 +77,7 @@ func TestSatisfies(t *testing.T) { errors.New("allowedList requires at least one element, but is empty")}, {"err - invalid license", "NON-EXISTENT-LICENSE", []string{"MIT", "Apache-2.0"}, false, errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, - {"err - invalid license in allowed list", "MIT", []string{"NON-EXISTENT-LICENSE", "Apache-2.0"}, false, + {"err - invalid license in allowed list", "Apache-1.0", []string{"NON-EXISTENT-LICENSE", "Apache-2.0"}, false, errors.New("unknown license 'NON-EXISTENT-LICENSE' at offset 0")}, {"MIT satisfies [MIT, Apache-2.0]", "MIT", []string{"MIT", "Apache-2.0"}, true, nil},