syft/imgbom/cataloger/java/parse_java_archive_test.go

256 lines
7.0 KiB
Go

package java
import (
"bufio"
"github.com/anchore/imgbom/imgbom/pkg"
"github.com/go-test/deep"
"github.com/gookit/color"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
"testing"
)
func generateJavaBuildFixture(t *testing.T, fixturePath string) {
if _, err := os.Stat(fixturePath); !os.IsNotExist(err) {
// fixture already exists...
return
}
makeTask := strings.TrimPrefix(fixturePath, "test-fixtures/java-builds/")
t.Logf(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
cwd, err := os.Getwd()
if err != nil {
t.Errorf("unable to get cwd: %+v", err)
}
cmd := exec.Command("make", makeTask)
cmd.Dir = filepath.Join(cwd, "test-fixtures/java-builds/")
stderr, err := cmd.StderrPipe()
if err != nil {
t.Fatalf("could not get stderr: %+v", err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
t.Fatalf("could not get stdout: %+v", err)
}
err = cmd.Start()
if err != nil {
t.Fatalf("failed to start cmd: %+v", err)
}
show := func(label string, reader io.ReadCloser) {
scanner := bufio.NewScanner(reader)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
t.Logf("%s: %s", label, scanner.Text())
}
}
go show("out", stdout)
go show("err", stderr)
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
// This works on both Unix and Windows. Although package
// syscall is generally platform dependent, WaitStatus is
// defined for both Unix and Windows and in both cases has
// an ExitStatus() method with the same signature.
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
if status.ExitStatus() != 0 {
t.Fatalf("failed to generate fixture: rc=%d", status.ExitStatus())
}
}
} else {
t.Fatalf("unable to get generate fixture result: %+v", err)
}
}
}
func TestParseJar(t *testing.T) {
tests := []struct {
fixture string
expected map[string]pkg.Package
ignoreExtras []string
}{
{
fixture: "test-fixtures/java-builds/packages/example-jenkins-plugin.hpi",
ignoreExtras: []string{"Plugin-Version"}, // has dynamic date
expected: map[string]pkg.Package{
"example-jenkins-plugin": {
Name: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT",
Language: pkg.Java,
Type: pkg.JenkinsPluginPkg,
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{
ManifestVersion: "1.0",
SpecTitle: "The Jenkins Plugins Parent POM Project",
ImplTitle: "example-jenkins-plugin",
ImplVersion: "1.0-SNAPSHOT",
Extra: map[string]string{
"Archiver-Version": "Plexus Archiver",
"Plugin-License-Url": "https://opensource.org/licenses/MIT",
"Plugin-License-Name": "MIT License",
"Created-By": "Apache Maven",
"Built-By": "?",
"Build-Jdk": "14.0.1",
"Jenkins-Version": "2.164.3",
"Minimum-Java-Version": "1.8",
"Plugin-Developers": "",
"Plugin-ScmUrl": "https://github.com/jenkinsci/plugin-pom/example-jenkins-plugin",
"Extension-Name": "example-jenkins-plugin",
"Short-Name": "example-jenkins-plugin",
"Group-Id": "io.jenkins.plugins",
"Plugin-Dependencies": "structs:1.20",
//"Plugin-Version": "1.0-SNAPSHOT (private-07/09/2020 13:30-?)",
"Hudson-Version": "2.164.3",
"Long-Name": "TODO Plugin",
},
},
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/io.jenkins.plugins/example-jenkins-plugin/pom.properties",
GroupID: "io.jenkins.plugins",
ArtifactID: "example-jenkins-plugin",
Version: "1.0-SNAPSHOT",
},
},
},
},
},
{
fixture: "test-fixtures/java-builds/packages/example-java-app-gradle-0.1.0.jar",
expected: map[string]pkg.Package{
"example-java-app-gradle": {
Name: "example-java-app-gradle",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{
ManifestVersion: "1.0",
},
},
},
},
},
{
fixture: "test-fixtures/java-builds/packages/example-java-app-maven-0.1.0.jar",
expected: map[string]pkg.Package{
"example-java-app-maven": {
Name: "example-java-app-maven",
Version: "0.1.0",
Language: pkg.Java,
Type: pkg.JavaPkg,
Metadata: pkg.JavaMetadata{
Manifest: &pkg.JavaManifest{
ManifestVersion: "1.0",
Extra: map[string]string{
"Archiver-Version": "Plexus Archiver",
"Created-By": "Apache Maven 3.6.3",
"Built-By": "?",
"Build-Jdk": "14.0.1",
"Main-Class": "hello.HelloWorld",
},
},
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/org.anchore/example-java-app-maven/pom.properties",
GroupID: "org.anchore",
ArtifactID: "example-java-app-maven",
Version: "0.1.0",
},
},
},
"joda-time": {
Name: "joda-time",
Version: "2.9.2",
Language: pkg.Java,
Type: pkg.UnknownPkg,
Metadata: pkg.JavaMetadata{
PomProperties: &pkg.PomProperties{
Path: "META-INF/maven/joda-time/joda-time/pom.properties",
GroupID: "joda-time",
ArtifactID: "joda-time",
Version: "2.9.2",
},
},
},
},
},
}
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
generateJavaBuildFixture(t, test.fixture)
fixture, err := os.Open(test.fixture)
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}
actual, err := parseJavaArchive(fixture.Name(), fixture)
if err != nil {
t.Fatalf("failed to parse java archive: %+v", err)
}
if len(actual) != len(test.expected) {
for _, a := range actual {
t.Log(" ", a)
}
t.Fatalf("unexpected package count: %d!=%d", len(actual), 1)
}
var parent *pkg.Package
for _, a := range actual {
if strings.Contains(a.Name, "example-") {
parent = &a
}
}
if parent == nil {
t.Fatal("could not find the parent pkg")
}
for _, a := range actual {
e, ok := test.expected[a.Name]
if !ok {
t.Errorf("entry not found: %s", a.Name)
continue
}
if a.Name != parent.Name && a.Metadata.(pkg.JavaMetadata).Parent != nil && a.Metadata.(pkg.JavaMetadata).Parent.Name != parent.Name {
t.Errorf("mismatched parent: %+v", a.Metadata.(pkg.JavaMetadata).Parent)
}
// we need to compare the other fields without parent attached
metadata := a.Metadata.(pkg.JavaMetadata)
metadata.Parent = nil
// ignore select fields
for _, field := range test.ignoreExtras {
delete(metadata.Manifest.Extra, field)
}
// write censored data back
a.Metadata = metadata
diffs := deep.Equal(a, e)
if len(diffs) > 0 {
t.Errorf("diffs found for %q", a.Name)
for _, d := range diffs {
t.Errorf("diff: %+v", d)
}
}
}
})
}
}