github.com/google/osv-scalibr@v0.4.1/semantic/compare_test.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package semantic_test
    16  
    17  import (
    18  	"bufio"
    19  	"errors"
    20  	"os"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/osv-scalibr/semantic"
    25  )
    26  
    27  func expectedResult(t *testing.T, comparator string) int {
    28  	t.Helper()
    29  
    30  	switch comparator {
    31  	case "<":
    32  		return -1
    33  	case "=":
    34  		return 0
    35  	case ">":
    36  		return +1
    37  	default:
    38  		t.Fatalf("unknown comparator %s", comparator)
    39  
    40  		return -999
    41  	}
    42  }
    43  
    44  func compareWord(t *testing.T, result int) string {
    45  	t.Helper()
    46  
    47  	switch result {
    48  	case 1:
    49  		return "greater than"
    50  	case 0:
    51  		return "equal to"
    52  	case -1:
    53  		return "less than"
    54  	default:
    55  		t.Fatalf("Unexpected compare result: %d\n", result)
    56  
    57  		return ""
    58  	}
    59  }
    60  
    61  func runAgainstEcosystemFixture(t *testing.T, ecosystem string, filename string) {
    62  	t.Helper()
    63  
    64  	file, err := os.Open("testdata/" + filename)
    65  	if err != nil {
    66  		t.Fatalf("Failed to read fixture file: %v", err)
    67  	}
    68  
    69  	defer file.Close()
    70  
    71  	scanner := bufio.NewScanner(file)
    72  
    73  	total := 0
    74  	failed := 0
    75  
    76  	for scanner.Scan() {
    77  		line := scanner.Text()
    78  
    79  		if line == "" ||
    80  			strings.HasPrefix(line, "# ") ||
    81  			strings.HasPrefix(line, "// ") {
    82  			continue
    83  		}
    84  
    85  		total++
    86  		pieces := strings.Split(line, " ")
    87  
    88  		if len(pieces) != 3 {
    89  			t.Fatalf(`incorrect number of peices in fixture "%s" (got %d)`, line, len(pieces))
    90  		}
    91  
    92  		result := expectEcosystemCompareResult(t, ecosystem, pieces[0], pieces[1], pieces[2])
    93  
    94  		if !result {
    95  			failed++
    96  		}
    97  	}
    98  
    99  	if failed > 0 {
   100  		t.Errorf("%d of %d failed", failed, total)
   101  	}
   102  
   103  	if err = scanner.Err(); err != nil {
   104  		t.Fatal(err)
   105  	}
   106  }
   107  
   108  func parseAsVersion(t *testing.T, str string, ecosystem string) semantic.Version {
   109  	t.Helper()
   110  
   111  	v, err := semantic.Parse(str, ecosystem)
   112  
   113  	if err != nil {
   114  		t.Fatalf("failed to parse version '%s' as ecosystem '%s': %v", str, ecosystem, err)
   115  	}
   116  
   117  	return v
   118  }
   119  
   120  func expectCompareResult(
   121  	t *testing.T,
   122  	ecosystem string,
   123  	a string,
   124  	b string,
   125  	expectedResult int,
   126  ) bool {
   127  	t.Helper()
   128  
   129  	v := parseAsVersion(t, a, ecosystem)
   130  
   131  	actualResult, err := v.CompareStr(b)
   132  
   133  	if err != nil {
   134  		t.Fatalf("failed to compare versions: %v", err)
   135  	}
   136  
   137  	if actualResult != expectedResult {
   138  		t.Errorf(
   139  			"Expected %s to be %s %s, but it was %s",
   140  			a,
   141  			compareWord(t, expectedResult),
   142  			b,
   143  			compareWord(t, actualResult),
   144  		)
   145  
   146  		return false
   147  	}
   148  
   149  	return true
   150  }
   151  
   152  func expectEcosystemCompareResult(
   153  	t *testing.T,
   154  	ecosystem string,
   155  	a string,
   156  	c string,
   157  	b string,
   158  ) (success bool) {
   159  	t.Helper()
   160  
   161  	success = success || expectCompareResult(t,
   162  		ecosystem, a, b,
   163  		+expectedResult(t, c),
   164  	)
   165  
   166  	success = success && expectCompareResult(t,
   167  		ecosystem, b, a,
   168  		-expectedResult(t, c),
   169  	)
   170  
   171  	return success
   172  }
   173  
   174  func TestVersion_Compare_Ecosystems(t *testing.T) {
   175  	tests := []struct {
   176  		name string
   177  		file string
   178  	}{
   179  		{
   180  			name: "npm",
   181  			file: "semver-versions.txt",
   182  		},
   183  		{
   184  			name: "crates.io",
   185  			file: "semver-versions.txt",
   186  		},
   187  		{
   188  			name: "RubyGems",
   189  			file: "rubygems-versions.txt",
   190  		},
   191  		{
   192  			name: "RubyGems",
   193  			file: "rubygems-versions-generated.txt",
   194  		},
   195  		{
   196  			name: "NuGet",
   197  			file: "nuget-versions.txt",
   198  		},
   199  		{
   200  			name: "Packagist",
   201  			file: "packagist-versions.txt",
   202  		},
   203  		{
   204  			name: "Packagist",
   205  			file: "packagist-versions-generated.txt",
   206  		},
   207  		{
   208  			name: "Go",
   209  			file: "semver-versions.txt",
   210  		},
   211  		{
   212  			name: "Hex",
   213  			file: "semver-versions.txt",
   214  		},
   215  		{
   216  			name: "Maven",
   217  			file: "maven-versions.txt",
   218  		},
   219  		{
   220  			name: "Maven",
   221  			file: "maven-versions-generated.txt",
   222  		},
   223  		{
   224  			name: "PyPI",
   225  			file: "pypi-versions.txt",
   226  		},
   227  		{
   228  			name: "PyPI",
   229  			file: "pypi-versions-generated.txt",
   230  		},
   231  		{
   232  			name: "Debian",
   233  			file: "debian-versions.txt",
   234  		},
   235  		{
   236  			name: "Debian",
   237  			file: "debian-versions-generated.txt",
   238  		},
   239  		{
   240  			name: "CRAN",
   241  			file: "cran-versions.txt",
   242  		},
   243  		{
   244  			name: "CRAN",
   245  			file: "cran-versions-generated.txt",
   246  		},
   247  		{
   248  			name: "Alpine",
   249  			file: "alpine-versions.txt",
   250  		},
   251  		{
   252  			name: "Alpine",
   253  			file: "alpine-versions-generated.txt",
   254  		},
   255  		{
   256  			name: "Red Hat",
   257  			file: "redhat-versions.txt",
   258  		},
   259  		{
   260  			name: "Hackage",
   261  			file: "hackage-versions.txt",
   262  		},
   263  		{
   264  			name: "Pub",
   265  			file: "pub-versions.txt",
   266  		},
   267  	}
   268  	for _, tt := range tests {
   269  		t.Run(tt.name, func(t *testing.T) {
   270  			runAgainstEcosystemFixture(t, tt.name, tt.file)
   271  		})
   272  	}
   273  }
   274  
   275  func TestVersion_Compare_Debian_InvalidVersion(t *testing.T) {
   276  	v := parseAsVersion(t, "1.2.3", "Debian")
   277  
   278  	_, err := v.CompareStr("1.2.3-not-a-debian:version!@#$")
   279  
   280  	if err == nil {
   281  		t.Fatalf("expected error comparing invalid version")
   282  	}
   283  
   284  	if !errors.Is(err, semantic.ErrInvalidVersion) {
   285  		t.Errorf("expected ErrInvalidVersion, got '%v'", err)
   286  	}
   287  }