Repository: google/osv-scanner
Pull request #162 (2023-01-22 22:56:44)

test: add case for `--version` flag
Increases coverage by 9% 😂 


Patch:
diff --git a/generators/GenerateMavenVersions.java b/generators/GenerateMavenVersions.java
deleted file mode 100644
index 7c8f6ba..0000000
--- a/generators/GenerateMavenVersions.java
+++ /dev/null
@@ -1,209 +0,0 @@
-import org.apache.maven.artifact.versioning.ComparableVersion;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-import java.io.*;
-import java.net.URL;
-import java.nio.channels.Channels;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.charset.StandardCharsets;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-
-/**
- * Script for generating a list of maven version comparison fixtures based off
- * every version mentioned in the OSV Maven database, sorted using the native
- * Maven implementation.
- * <p>
- * To run this, you need to ensure copies of the following libraries are present
- * on the class path:
- *
- * <ul>
- * <li><a href="https://search.maven.org/artifact/org.json/json/20220924/bundle"><code>json</code></a></li>
- * <li><a href="https://search.maven.org/artifact/org.apache.maven/maven-artifact/3.8.6/jar"><code>maven-artifact</code></a></li>
- * </ul>
- * The easiest way to do this is by putting the jars into a <code>lib</code> subfolder and then running:
- * <code>
- *   java -cp generators/lib/* generators/GenerateMavenVersions.java
- * </code>
- */
-public class GenerateMavenVersions {
-  public static String downloadMavenDb() throws IOException {
-    URL website = new URL("https://osv-vulnerabilities.storage.googleapis.com/Maven/all.zip");
-    String file = "./maven-db.zip";
-
-    ReadableByteChannel rbc = Channels.newChannel(website.openStream());
-
-    try(FileOutputStream fos = new FileOutputStream(file)) {
-      fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
-    }
-
-    return file;
-  }
-
-  public static Map<String, List<String>> fetchPackageVersions() throws IOException {
-    String dbPath = downloadMavenDb();
-    List<JSONObject> osvs = loadOSVs(dbPath);
-
-    Map<String, List<String>> packages = new HashMap<>();
-
-    osvs.forEach(osv -> osv.getJSONArray("affected").forEach(aff -> {
-      JSONObject affected = (JSONObject) aff;
-
-      String pkgName = affected.getJSONObject("package").getString("name");
-
-      if(!affected.has("versions")) {
-        return;
-      }
-      JSONArray versions = affected.getJSONArray("versions");
-
-      packages.putIfAbsent(pkgName, new ArrayList<>());
-
-      if(versions.isEmpty()) {
-        return;
-      }
-
-      versions.forEach(version -> packages.get(pkgName).add((String) version));
-    }));
-
-    packages.forEach((key, _ignore) -> packages.put(
-      key,
-      packages.get(key)
-              .stream()
-              .distinct()
-              .sorted(Comparator.comparing(ComparableVersion::new))
-              .collect(Collectors.toList())
-    ));
-
-    return packages;
-  }
-
-  public static List<JSONObject> loadOSVs(String pathToDbZip) throws IOException {
-    List<JSONObject> osvs = new ArrayList<>();
-
-    try(ZipFile zipFile = new ZipFile(pathToDbZip)) {
-      Enumeration<? extends ZipEntry> entries = zipFile.entries();
-
-      while(entries.hasMoreElements()) {
-        ZipEntry entry = entries.nextElement();
-        InputStream stream = zipFile.getInputStream(entry);
-
-        BufferedReader streamReader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
-        StringBuilder responseStrBuilder = new StringBuilder();
-
-        String inputStr;
-        while((inputStr = streamReader.readLine()) != null) {
-          responseStrBuilder.append(inputStr);
-        }
-        osvs.add(new JSONObject(responseStrBuilder.toString()));
-      }
-    }
-
-    return osvs;
-  }
-
-  public static void writeToFile(String outfile, List<String> lines) throws IOException {
-    try(PrintWriter writer = new PrintWriter(outfile, StandardCharsets.UTF_8)) {
-      lines.forEach(writer::println);
-    }
-  }
-
-  public static boolean compareVers(String version1, String op, String version2) {
-    ComparableVersion v1 = new ComparableVersion(version1);
-    ComparableVersion v2 = new ComparableVersion(version2);
-
-    int r = v1.compareTo(v2);
-
-    if(op.equals("=")) {
-      return r == 0;
-    }
-
-    if(op.equals("<")) {
-      return r < 0;
-    }
-
-    if(op.equals(">")) {
-      return r > 0;
-    }
-
-    throw new RuntimeException("unsupported comparison operator " + op);
-  }
-
-  public static void compareVersions(List<String> lines, String select) {
-    lines.forEach(line -> {
-      line = line.trim();
-
-      if(line.isEmpty() || line.startsWith("#") || line.startsWith("//")) {
-        return;
-      }
-
-      String[] parts = line.split(" ");
-      String v1 = parts[0];
-      String op = parts[1];
-      String v2 = parts[2];
-
-      boolean r = compareVers(v1, op, v2);
-
-      if(select.equals("failures") && r) {
-        return;
-      }
-
-      if(select.equals("successes") && !r) {
-        return;
-      }
-
-      String color = r ? "\033[92m" : "\033[91m";
-      String rs = r ? "T" : "F";
-
-      System.out.printf("%s%s\033[0m: \033[93m%s\033[0m\n", color, rs, line);
-    });
-  }
-
-  public static void compareVersionsInFile(String filepath, String select) throws IOException {
-    List<String> lines = new ArrayList<>();
-
-    try(BufferedReader br = new BufferedReader(new FileReader(filepath))) {
-      String line = br.readLine();
-
-      while(line != null) {
-        lines.add(line);
-        line = br.readLine();
-      }
-    }
-
-    compareVersions(lines, select);
-  }
-
-  public static List<String> generateVersionCompares(List<String> versions) {
-    return IntStream.range(1, versions.size()).mapToObj(i -> {
-      String currentVersion = versions.get(i);
-      String previousVersion = versions.get(i - 1);
-      String op = compareVers(currentVersion, "=", previousVersion) ? "=" : "<";
-
-      return String.format("%s %s %s", previousVersion, op, currentVersion);
-    }).collect(Collectors.toList());
-  }
-
-  public static List<String> generatePackageCompares(Map<String, List<String>> packages) {
-    return packages
-             .values()
-             .stream()
-             .map(GenerateMavenVersions::generateVersionCompares)
-             .flatMap(Collection::stream)
-             .distinct()
-             .collect(Collectors.toList());
-  }
-
-  public static void main(String[] args) throws IOException {
-    String outfile = "maven-versions-generated.txt";
-    Map<String, List<String>> packages = fetchPackageVersions();
-
-    writeToFile(outfile, generatePackageCompares(packages));
-
-    compareVersionsInFile(outfile, "failures");
-  }
-}
diff --git a/generators/generate-debian-versions.py b/generators/generate-debian-versions.py
deleted file mode 100755
index b0abc62..0000000
--- a/generators/generate-debian-versions.py
+++ /dev/null
@@ -1,188 +0,0 @@
-#!/usr/bin/env python3
-
-import zipfile
-import operator
-import urllib.request
-import json
-import subprocess
-from pathlib import Path
-
-
-# this requires being run on an OS that has a version of "dpkg" which supports the
-# "--compare-versions" option; also make sure to consider the version of dpkg being
-# used in case there are changes to the comparing logic (last run with 1.19.7).
-#
-# also note that because of the large amount of versions being used there is
-# significant overhead in having to use a subprocess, so this generator caches
-# the results of said subprocess calls; a typical no-cache run takes about 5+
-# minutes whereas with the cache it only takes seconds.
-
-def download_debian_db():
-  urllib.request.urlretrieve("https://osv-vulnerabilities.storage.googleapis.com/Debian/all.zip", "debian-db.zip")
-
-
-def extract_packages_with_versions(osvs):
-  dict = {}
-
-  for osv in osvs:
-    for affected in osv['affected']:
-      package = affected['package']['name']
-
-      if package not in dict:
-        dict[package] = []
-
-      for version in affected.get('versions', []):
-        dict[package].append(DebianVersion(version))
-
-  # deduplicate and sort the versions for each package
-  for package in dict:
-    dict[package] = sorted(list(dict.fromkeys(dict[package])))
-
-  return dict
-
-
-class DebianVersionComparer:
-  def __init__(self, cache_path):
-    self.cache_path = Path(cache_path)
-    self.cache = {}
-
-    self._load_cache()
-
-  def _load_cache(self):
-    if self.cache_path:
-      self.cache_path.touch()
-      with open(self.cache_path, "r") as f:
-        lines = f.readlines()
-
-        for line in lines:
-          line = line.strip()
-          key, result = line.split(",")
-
-          if result == "True":
-            self.cache[key] = True
-            continue
-          if result == "False":
-            self.cache[key] = False
-            continue
-
-          print(f"ignoring invalid cache entry '{line}'")
-
-  def _save_to_cache(self, key, result):
-    self.cache[key] = result
-    if self.cache_path:
-      self.cache_path.touch()
-      with open(self.cache_path, "a") as f:
-        f.write(f"{key},{result}\n")
-
-  def compare(self, a, op, b):
-    key = f"{a} {op} {b}"
-    if key in self.cache:
-      return self.cache[key]
-
-    cmd = ["dpkg", "--compare-versions", a, op, b]
-    out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
-    if out.stdout:
-      print(out.stdout.decode('utf-8'))
-    if out.stderr:
-      print(out.stderr.decode('utf-8'))
-
-    r = out.returncode == 0
-    self._save_to_cache(key, r)
-    return r
-
-
-debian_comparer = DebianVersionComparer("/tmp/debian-versions-generator-cache.csv")
-
-
-class DebianVersion:
-  def __str__(self):
-    return self.version
-
-  def __hash__(self):
-    return hash(self.version)
-
-  def __init__(self, version):
-    self.version = version
-
-  def __lt__(self, other):
-    return debian_comparer.compare(self.version, 'lt', other.version)
-
-  def __gt__(self, other):
-    return debian_comparer.compare(self.version, 'gt', other.version)
-
-  def __eq__(self, other):
-    return debian_comparer.compare(self.version, 'eq', other.version)
-
-
-def compare(v1, relate, v2):
-  ops = {'<': operator.lt, '=': operator.eq, '>': operator.gt}
-  return ops[relate](v1, v2)
-
-
-def compare_versions(lines, select="all"):
-  for line in lines:
-    line = line.strip()
-
-    if line == "" or line.startswith('#') or line.startswith('//'):
-      continue
-
-    v1, op, v2 = line.strip().split(" ")
-
-    r = compare(DebianVersion(v1), op, DebianVersion(v2))
-
-    if select == "failures" and r:
-      continue
-
-    if select == "successes" and not r:
-      continue
-
-    color = '\033[92m' if r else '\033[91m'
-    rs = "T" if r else "F"
-    print(f"{color}{rs}\033[0m: \033[93m{line}\033[0m")
-
-
-def compare_versions_in_file(filepath, select="all"):
-  with open(filepath) as f:
-    lines = f.readlines()
-    compare_versions(lines, select)
-
-
-def generate_version_compares(versions):
-  comparisons = []
-  for i, version in enumerate(versions):
-    if i == 0:
-      continue
-    comparisons.append(f"{versions[i - 1]} < {version}\n")
-  return comparisons
-
-
-def generate_package_compares(packages):
-  comparisons = []
-  for package in packages:
-    versions = packages[package]
-    comparisons.extend(generate_version_compares(versions))
-
-  # return comparisons
-  return list(dict.fromkeys(comparisons))
-
-
-def fetch_packages_versions():
-  download_debian_db()
-  osvs = []
-
-  with zipfile.ZipFile('debian-db.zip') as db:
-    for fname in db.namelist():
-      with db.open(fname) as osv:
-        osvs.append(json.loads(osv.read().decode('utf-8')))
-
-  return extract_packages_with_versions(osvs)
-
-
-outfile = "internal/semantic/fixtures/debian-versions-generated.txt"
-
-packs = fetch_packages_versions()
-with open(outfile, "w") as f:
-  f.writelines(generate_package_compares(packs))
-
-compare_versions_in_file(outfile, "failures")
diff --git a/generators/generate-packagist-versions.php b/generators/generate-packagist-versions.php
deleted file mode 100644
index c5602cb..0000000
--- a/generators/generate-packagist-versions.php
+++ /dev/null
@@ -1,169 +0,0 @@
-<?php
-
-function downloadPackagistDb(): string
-{
-  $url = 'https://osv-vulnerabilities.storage.googleapis.com/Packagist/all.zip';
-
-  $file    = './packagist-db.zip';
-  $current = file_get_contents($url);
-  file_put_contents($file, $current);
-
-  return $file;
-}
-
-/**
- * @throws RuntimeException
- */
-function openDbZip(string $path): ZipArchive
-{
-  $zip = new ZipArchive();
-  if ($zip->open($path, ZipArchive::RDONLY) === false) {
-    throw new RuntimeException('failed to read zip archive');
-  }
-
-  return $zip;
-}
-
-/**
- * @throws JsonException
- * @throws RuntimeException
- */
-function fetchPackageVersions(): array
-{
-  $dbPath = downloadPackagistDb();
-  $dbZip  = openDbZip($dbPath);
-
-  $osvs = [];
-
-  for ($i = 0; $i < $dbZip->numFiles; $i++) {
-    $file = $dbZip->getFromIndex($i);
-
-    if ($file === false) {
-      throw new RuntimeException('failed to read a file from db zip');
-    }
-
-    $osvs[] = json_decode($file, true, 512, JSON_THROW_ON_ERROR);
-  }
-
-  $packages = [];
-
-  foreach ($osvs as $osv) {
-    foreach ($osv['affected'] as $affected) {
-      $package = $affected['package']['name'];
-
-      if (!isset($packages[$package])) {
-        $packages[$package] = [];
-      }
-
-      if (empty($affected['versions'])) {
-        continue;
-      }
-
-      foreach ($affected['versions'] as $version) {
-        $packages[$package][] = $version;
-      }
-    }
-  }
-
-  return array_map(static function ($versions) {
-    $uniq = array_unique($versions);
-    usort($uniq, static fn($a, $b) => version_compare(ltrim($a, "vV"), ltrim($b, "vV")));
-
-    return $uniq;
-  }, $packages);
-}
-
-/**
- * Normalizes the previous version such that it will compare "correctly" to the current version,
- * by ensuring that they both have the same "v" prefix (or lack of).
- *
- * Whether the "v" prefix is present on the normalized previous version depends on
- * its presences in the current version; this ensure we will have _some_ versions that
- * do have the "v" prefix, rather than it being present on _none_ or _all_ versions.
- *
- * @param string $currentVersion
- * @param string $previousVersion
- *
- * @return string
- */
-function normalizePrevVersion(string $currentVersion, string $previousVersion): string
-{
-  if (str_starts_with($currentVersion, "v")) {
-    $previousVersion = ltrim($previousVersion, "vV");
-
-    return "v$previousVersion";
-  }
-
-  if (str_starts_with($currentVersion, "V")) {
-    $previousVersion = ltrim($previousVersion, "vV");
-
-    return "V$previousVersion";
-  }
-
-  return ltrim($previousVersion, "vV");
-}
-
-function generateVersionCompares(array $versions): array
-{
-  $comparisons = [];
-
-  foreach ($versions as $index => $version) {
-    if ($index === 0) {
-      continue;
-    }
-
-    $prevVersion = normalizePrevVersion($version, $versions[$index - 1]);
-    $op          = version_compare($prevVersion, $version) === 0 ? "=" : "<";
-
-    $comparisons[] = "$prevVersion $op $version";
-  }
-
-  return $comparisons;
-}
-
-function generatePackageCompares(array $packages): array
-{
-  $comparisons = [];
-
-  foreach ($packages as $versions) {
-    $comparisons[] = generateVersionCompares($versions);
-  }
-
-  return array_merge(...$comparisons);
-}
-
-function compareVersions(array $lines, string $select = "all"): void
-{
-  foreach ($lines as $line) {
-    $line = trim($line);
-
-    if (empty($line) || str_starts_with($line, "#") || str_starts_with($line, "//")) {
-      continue;
-    }
-
-    [$v1, $op, $v2] = explode(" ", $line);
-
-    $r = version_compare($v1, $v2, $op);
-
-    if ($select === "failures" && $r === true) {
-      continue;
-    }
-
-    if ($select === "successes" && $r !== true) {
-      continue;
-    }
-
-    $color = $r ? "\033[92m" : "\033[91m";
-    $rs    = $r ? "T" : "F";
-    echo "$color$rs\033[0m: \033[93m$line\033[0m\n";
-  }
-}
-
-$outfile = "packagist-versions-generated.txt";
-
-/** @noinspection PhpUnhandledExceptionInspection */
-$packages = fetchPackageVersions();
-
-file_put_contents($outfile, implode("\n", array_unique(generatePackageCompares($packages))));
-
-compareVersions(explode("\n", file_get_contents($outfile)), "failures");
diff --git a/generators/generate-pypi-versions.py b/generators/generate-pypi-versions.py
deleted file mode 100755
index cb9886a..0000000
--- a/generators/generate-pypi-versions.py
+++ /dev/null
@@ -1,110 +0,0 @@
-#!/usr/bin/env python3
-
-import packaging.version
-import zipfile
-import operator
-import urllib.request
-import json
-
-
-# this requires you run "pip install packaging" - have to be careful about versions too
-# because of the "legacy version" stuff
-
-def download_pypi_db():
-  urllib.request.urlretrieve("https://osv-vulnerabilities.storage.googleapis.com/PyPI/all.zip", "pypi-db.zip")
-
-
-def extract_packages_with_versions(osvs):
-  dict = {}
-
-  for osv in osvs:
-    for affected in osv['affected']:
-      package = affected['package']['name']
-
-      if package not in dict:
-        dict[package] = []
-
-      for version in affected.get('versions', []):
-        try:
-          dict[package].append(packaging.version.parse(version))
-        except packaging.version.InvalidVersion:
-          print(f"skipping invalid version {version} for {package}")
-
-  # deduplicate and sort the versions for each package
-  for package in dict:
-    dict[package] = sorted(list(dict.fromkeys(dict[package])))
-
-  return dict
-
-
-def compare(v1, relate, v2):
-  ops = {'<': operator.lt, '=': operator.eq, '>': operator.gt}
-  return ops[relate](v1, v2)
-
-
-def compare_versions(lines, select="all"):
-  for line in lines:
-    line = line.strip()
-
-    if line == "" or line.startswith('#') or line.startswith('//'):
-      continue
-
-    v1, op, v2 = line.strip().split(" ")
-
-    r = compare(packaging.version.parse(v1), op, packaging.version.parse(v2))
-
-    if select == "failures" and r:
-      continue
-
-    if select == "successes" and not r:
-      continue
-
-    color = '\033[92m' if r else '\033[91m'
-    rs = "T" if r else "F"
-    print(f"{color}{rs}\033[0m: \033[93m{line}\033[0m")
-
-
-def compare_versions_in_file(filepath, select="all"):
-  with open(filepath) as f:
-    lines = f.readlines()
-    compare_versions(lines, select)
-
-
-def generate_version_compares(versions):
-  comparisons = []
-  for i, version in enumerate(versions):
-    if i == 0:
-      continue
-    comparisons.append(f"{versions[i - 1]} < {version}\n")
-  return comparisons
-
-
-def generate_package_compares(packages):
-  comparisons = []
-  for package in packages:
-    versions = packages[package]
-    comparisons.extend(generate_version_compares(versions))
-
-  # return comparisons
-  return list(dict.fromkeys(comparisons))
-
-
-def fetch_packages_versions():
-  download_pypi_db()
-  osvs = []
-
-  with zipfile.ZipFile('pypi-db.zip') as db:
-    for fname in db.namelist():
-      with db.open(fname) as osv:
-        osvs.append(json.loads(osv.read().decode('utf-8')))
-
-  return extract_packages_with_versions(osvs)
-
-
-outfile = "internal/semantic/fixtures/pypi-versions-generated.txt"
-
-packs = fetch_packages_versions()
-with open(outfile, "w") as f:
-  f.writelines(generate_package_compares(packs))
-
-compare_versions_in_file(outfile, "failures")
diff --git a/generators/generate-rubygems-versions.rb b/generators/generate-rubygems-versions.rb
deleted file mode 100755
index 0ee5cd5..0000000
--- a/generators/generate-rubygems-versions.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-#!/usr/bin/env ruby
-
-require "rubygems/version"
-require "open-uri"
-require "json"
-require "zip"
-
-def download_rubygems_db
-  URI.open("https://osv-vulnerabilities.storage.googleapis.com/RubyGems/all.zip") do |zip|
-    File.open("rubygems-db.zip", "wb") { |f| f.write(zip.read) }
-  end
-end
-
-def extract_packages_with_versions(osvs)
-  packages = {}
-
-  osvs.each do |osv|
-    osv["affected"].each do |affected|
-      package = affected["package"]["name"]
-
-      packages[package] ||= []
-      affected.fetch("versions", []).each do |version|
-        packages[package] << Gem::Version.new(version)
-      end
-    end
-  end
-
-  packages.map { |k, v| [k, v.uniq.sort] }.to_h
-end
-
-def compare_version(v1, op, v2)
-  op = "==" if op == "="
-
-  Gem::Version.new(v1).method(op).call(Gem::Version.new(v2))
-end
-
-# @param [Array<String>] lines
-def compare_versions(lines, select = :all)
-  lines.each do |line|
-    line = line.strip
-
-    next if line.empty? || line.start_with?("#") || line.start_with?("//")
-
-    parts = line.split(" ")
-    v1 = parts[0]
-    op = parts[1]
-    v2 = parts[2]
-
-    r = compare_version(v1, op, v2)
-
-    next if select == :failures && r == true
-    next if select == :successes && r != true
-
-    color = r ? "\033[92m" : "\033[91m"
-    rs = r ? "T" : "F"
-    puts "#{color}#{rs}\033[0m: \033[93m#{line}\033[0m"
-  end
-end
-
-def compare_versions_in_file(filepath, select = :all)
-  compare_versions(File.readlines(filepath), select)
-end
-
-def generate_version_compares(versions)
-  comparisons = []
-
-  versions.each_with_index do |version, i|
-    next if i == 0
-
-    op = "<"
-    op = "=" if versions[i - 1] == version
-
-    comparisons << "#{versions[i - 1]} #{op} #{version}"
-  end
-
-  comparisons
-end
-
-def generate_package_compares(packages)
-  comparisons = []
-
-  packages.each { |_, versions| comparisons.concat(generate_version_compares(versions)) }
-
-  comparisons
-end
-
-def fetch_packages_versions
-  download_rubygems_db
-
-  osvs = Zip::File.open("rubygems-db.zip").map { |f| JSON.load(f.get_input_stream.read) }
-
-  extract_packages_with_versions(osvs)
-end
-
-outfile = "internal/semantic/fixtures/rubygems-versions-generated.txt"
-
-packs = fetch_packages_versions
-
-File.open(outfile, "w") { |f| f.write(generate_package_compares(packs).uniq.join("\n")) }
-
-compare_versions_in_file(outfile, :failures)


Test patch:
diff --git a/cmd/osv-scanner/main_test.go b/cmd/osv-scanner/main_test.go
index e0ac75c..0fb47de 100644
--- a/cmd/osv-scanner/main_test.go
+++ b/cmd/osv-scanner/main_test.go
@@ -109,6 +109,17 @@ func TestRun(t *testing.T) {
         No package sources found, --help for usage information.
 			`,
 		},
+		{
+			name:         "",
+			args:         []string{"", "--version"},
+			wantExitCode: 0,
+			wantStdout:   `
+				osv-scanner version: dev
+				commit: n/a
+				built at: n/a
+			`,
+			wantStderr: "",
+		},
 		// one specific supported lockfile
 		{
 			name:         "",