拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Syft Java Cataloger 的 Jar-Metadata 测试夹具:用目录结构代替二进制 Jar 的回归测试工程实践

Syft Java Cataloger 的 Jar-Metadata 测试夹具用目录结构代替二进制 Jar 的回归测试工程实践【免费下载链接】syftCLI tool and library for generating a Software Bill of Materials from container images and filesystems项目地址: https://gitcode.com/GitHub_Trending/sy/syftSyft 的 Java 包目录器cataloger需要从 jar 包中解析 Maven 元数据、MANIFEST.MF 与 POM 信息来生成软件物料清单SBOM。为了在不向仓库提交二进制文件的前提下完成对这些解析逻辑的回归测试Syft 在 syft/pkg/cataloger/java/testdata/jar-metadata 目录下设计了一套用目录结构模拟 jar的测试夹具体系。本文将以该目录中的 README 为骨架结合 Makefile 与 archive_parser_test.go、archive_parser.go 源码完整讲解这套夹具的设计动机、目录规范、构建流程以及它如何支撑多个真实回归场景如 PR 2231 的多 POM 父级解析、issue #2130 的重复 jar 回归并给出为 Syft 新增 Java 测试夹具的实操方法。设计动机为什么用目录代替真实 Jar在 README 的开篇Syft 明确说明了这套夹具的核心约定Each directory is the name of a jar to be created (simply a zip) based on the contents of the directory. This prevents us from having to create real jars by hand or keep binaries in the repo. This also means we dont need the entire jar, only the necessary metadata for testing.翻译过来即三条关键设计原则目录即 jarjar-metadata/下的每个子目录其名字就是要生成的 jar 文件名目录内的内容按原样打包zip即得到测试用的 jar不引入二进制测试夹具在生成前只是一堆纯文本文件MANIFEST.MF、pom.xml、pom.properties 等避免向仓库提交二进制 jar既减小仓库体积又让 fixture 可读、可 diff、可 review只保留必要元数据测试目标只是 jar 内部的元数据解析因此不需要完整 jar类文件、资源文件等均可省略只保留能触发被测代码路径的最小集合。这套约定使得新增一个测试用例的成本极低开发者只需按 jar 的真实内部结构摆好目录和文本文件运行一次 make 即可得到可被解析器消费的 jar 夹具。目录规范与夹具清单jar-metadata/下每个子目录即一个 fixture。当前仓库中实际存在的 fixture部分未在 README 中逐一展开包括夹具目录生成的 jar覆盖的测试场景api-all-2.0.0-sources/api-all-2.0.0-sources.jar多 POM 父级解析回归PR 2231jackson-core-2.15.2/jackson-core-2.15.2.jar重复 jar 回归issue #2130go 侧com.fasterxml.jackson.core.jackson-core-2.15.2/com.fasterxml.jackson.core.jackson-core-2.15.2.jar重复 jar 回归issue #2130bad 侧micronaut-aop-4.9.11/micronaut-aop-4.9.11.jar基于 Manifest 版本属性的命名/版本解析commons-lang3-3.12.0/commons-lang3-3.12.0.jarPOM 属性解析不 panic 的边界场景spring-instrumentation-4.3.0-1.0/spring-instrumentation-4.3.0-1.0.jarWeave-Classes 排除逻辑jenkins-plugins/gradle/2.11/gradle.hpiJenkins 插件包类型识别multiple-matching-2.11.5/multiple-matching-2.11.5.jar多 pom.properties 的确定性匹配org.multiple-thename/org.multiple-thename.jar多 groupId/同 artifactId 的确定性匹配uber-app-version-properties/uber-app-version-properties.jar从 version.properties 兜底取版本uber-app-version-placeholder/uber-app-version-placeholder.jar版本占位符场景lib-with-version-properties/lib-with-version-properties.jar库级 version.properties 场景opensaml-core-3.4.6/opensaml-core-3.4.6.jar含 INDEX.LIST 的常规 Maven 结构每个 fixture 内部严格模仿真实 Maven jar 的结构。以api-all-2.0.0-sources/为例META-INF/MANIFEST.MF声明Manifest-Version: 1.0、Created-By: Apache Maven 3.6.0、Build-Jdk: 1.8.0_191等标准主段属性META-INF/maven/org.apache.directory.api/api-all/pom.propertiesMaven 打包时自动生成的属性文件内容为version2.0.0、groupIdorg.apache.directory.api、artifactIdapi-allMETA-INF/maven/org.apache.directory.api/api-all/pom.xml对应的 POM 文件关键的是该目录下同时存在第二个 Maven 元数据块META-INF/maven/org.apache.directory.api/api-asn1-api/含独立的pom.properties与pom.xml这正是该夹具存在的意义——一个 jar 里嵌套了多个 Maven 构件。而jackson-core-2.15.2/则只保留META-INF/MANIFEST.MF与META-INF/maven/com.fasterxml.jackson.core/jackson-core/pom.xml其 pom.xml 包含了parentcom.fasterxml.jackson:jackson-base:2.15.2、licenses、url等解析器需要的信息用于复现 issue #2130。构建流程Makefile 驱动的 fixture 生成夹具的构建由 Makefile 统一管理它定义了四个核心目标goalREADME 中目录即 zip的思想在此落地1.fixtures默认目标生成全部夹具.DEFAULT_GOAL : fixtures fixtures: $(CACHE_DIR)$(CACHE_DIR)值为cache依赖一组 jar 文件例如$(CACHE_DIR)/$(JACKSON_CORE).jar: mkdir -p $(CACHE_DIR) cd $(JACKSON_CORE) zip -r $(CACHE_PATH)/$(JACKSON_CORE).jar .每个 fixture 的生成方式完全一致进入对应目录用zip -r把整个目录递归打包为同名.jar输出到jar-metadata/cache/。Makefile 中预置了 12 个命名常量如JACKSON_CORE、API_ALL_SOURCES、MICRONAUT_AOP等每新增一个夹具目录只需仿照既有规则追加一条 target。2.fingerprint基于源码指纹判定缓存是否需要失效FINGERPRINT_FILE$(CACHE_DIR).fingerprint $(FINGERPRINT_FILE): find . ! -path */cache* -type f -exec sha256sum {} \; | sort -k2 $(FINGERPRINT_FILE)对目录下所有非 cache 文件做sha256sum并排序写入cache.fingerprint。任何夹具源文件内容变化都会导致指纹变化从而让上层 CI/测试判定既有 cache 应被重建该目标被声明为.PHONY因此每次都会重新计算保证指纹永远基于当前源码。3. 特殊夹具Jenkins 插件gradle.hpi# Jenkins plugins typically do not have the version included in the archive name, # so it is important to not include it in the generated test fixture $(CACHE_DIR)/gradle.hpi: mkdir -p $(CACHE_DIR) cd jenkins-plugins/gradle/2.11 zip -r $(CACHE_PATH)/gradle.hpi .Jenkins 插件文件名如gradle.hpi通常不携带版本号因此夹具刻意保持这一命名特征以验证解析器能从 MANIFEST 的Plugin-Version等属性而非文件名推导版本。4.clean清除所有生成物clean: rm -rf $(CACHE_DIR)/* $(FINGERPRINT_FILE)测试侧的一体化调用测试运行时并不要求 cache 预先存在。archive_parser_test.go 中的generateJavaMetadataJarFixture会先检查testdata/jar-metadata/cache/fixtureName.ext是否存在不存在时直接执行make 目标在jar-metadata/目录下现场生成从而保证测试既能在干净检出fresh clone下运行也避免重复构建func generateJavaMetadataJarFixture(t *testing.T, fixtureName string, fileExtension string) string { if fileExtension { fileExtension jar } fixturePath : filepath.Join(testdata/jar-metadata/cache/, fixtureName.fileExtension) if _, err : os.Stat(fixturePath); !os.IsNotExist(err) { return fixturePath } makeTask : filepath.Join(cache, fixtureName.fileExtension) cmd : exec.Command(make, makeTask) cmd.Dir filepath.Join(cwd, testdata/jar-metadata) run(t, cmd) return fixturePath }回归场景一api-all-2.0.0-sources —— 多 POM 下的父级解析PR 2231README 中对该夹具的说明This fixture is built to simulate the case where we have a jar with multiple pom files discovered when trying to determine the parent. This is a valid case, but not one that we covered before PR 2231.该夹具模拟的真实世界场景是一个 jar 中嵌入多个 Maven 构件的 POM例如api-all聚合包把api-asn1-api等子构件的元数据一并打进 jar。解析器在确定主包main package时必须从多个 POM 中选出正确的那个并建立子包对父包的依赖关系而 PR 2231 之前这段逻辑并未被覆盖。测试用例Test_parseJavaArchive_regressions中对应multiple pom for parent selection regression (pr 2231)用例archive_parser_test.go断言从api-all-2.0.0-sources.jar中应解析出两个包api-allpkg:maven/org.apache.directory.api/api-all2.0.0其JavaArchive元数据同时带有Manifest、PomPropertiesMETA-INF/maven/org.apache.directory.api/api-all/pom.properties与PomProject含Parent: api-parent:2.0.0api-asn1-apipkg:maven/org.apache.directory.api/api-asn1-api2.0.0其PomProject的 Parent 为api-asn1-parent:2.0.0二者之间应存在一条DependencyOfRelationship关系即api-asn1-api依赖api-all这与聚合包中主构件包含子构件的真实语义一致。测试中还通过assignParent(tt.expectedPkgs[0], tt.expectedPkgs[1:]...)显式建立Parent字段的预期值archive_parser_test.go验证解析器在发现多个 POM 时对父级归属的正确选择。回归场景二jackson-core-2.15.2 —— 重复 jar 回归issue #2130README 中对这两个夹具的说明These two fixtures are built to simulate the case where we would have a duplicate jar regression as seen in issue #2130.jackson-core-2.15.2与com.fasterxml.jackson.core.jackson-core-2.15.2是两个内容几乎相同的夹具仅目录/文件名不同。它们复现的是同一个构件可能以两种命名形式出现在文件系统中——Maven 仓库风格jackson-core-2.15.2.jar以及 SBT/Ivy 风格以完整 groupId 拼接文件名的com.fasterxml.jackson.core.jackson-core-2.15.2.jar。如果解析逻辑对文件名解析与 POM 解析的结果去重/合并处理不当就会出现重复包duplicate jar的回归。对应测试用例archive_parser_test.go包含两例duplicate jar regression - go case (issue #2130)输入jackson-core-2.15.2.jar期望产出一个jackson-core2.15.15实际为 2.15.2包duplicate jar regression - bad case (issue #2130)输入com.fasterxml.jackson.core.jackson-core-2.15.2.jar期望产出同样且唯一的一个包。两例的JavaArchive元数据预期一致Manifest 中完整记录了 OSGi 与构建信息Bundle-SymbolicName、Bundle-Version、Implementation-Version、Created-By: Apache Maven Bundle Plugin 5.1.8等PomProject记录了jackson-base:2.15.2父 POM 与 Apache-2.0 许可证并断言许可证来自 jar 位置pkg.NewLicensesFromLocationWithContext。测试通过cmpopts.IgnoreFields(pkg.JavaArchive{}, ArchiveDigests)忽略归档摘要字段聚焦元数据语义的一致性。其余夹具背后的解析逻辑Weave-Classes 排除spring-instrumentation-4.3.0-1.0spring-instrumentation-4.3.0-1.0/META-INF/MANIFEST.MF中带有Weave-Classes: org/springframework/web/bind/annotation/GetMapping,...属性。解析器在 archive_parser.go 中据此直接跳过整个归档// check for existence of Weave-Classes manifest key in order to exclude jars getting misrepresented as // their targeted counterparts, e.g. newrelic spring and tomcat instrumentation if _, ok : manifest.Main.Get(Weave-Classes); ok { log.Debugf(excluding archive due to Weave-Classes manifest entry: %s, j.location) return nil, nil }原因注释写得很清楚这类instrumentation jar如果被当作其目标类库如 Spring、Tomcat 组件解析会产生错误的 SBOM 包。测试用例exclude instrumentation jars with Weave-Classes in manifest的预期结果就是expectedPkgs: nilarchive_parser_test.go。包类型识别gradle.hpi 的 Jenkins 插件jenkins-plugins/gradle/2.11/META-INF/MANIFEST.MF声明了Plugin-Version: 2.11、Group-Id: org.jenkins-ci.plugins、Short-Name: gradle、Extension-Name: gradle、Jenkins-Version: 2.303.3等 Jenkins 插件专属属性。解析器通过 archive_filename.go 与 archive_parser.go 将该归档识别为pkg.JenkinsPluginPkgPURL 为pkg:maven/org.jenkins-ci.plugins/gradle2.11对应测试用例Jenkins plugins assigned jenkins-plugin package typearchive_parser_test.go。该夹具刻意使用.hpi扩展名通过fileExtension: hpi传入以覆盖非 jar 归档的解析路径。命名与版本解析的优先级micronaut-aop、uber-app、lib-with-versionarchive_parser.go 中discoverNameVersionLicense对命名与版本的来源定义了明确优先级pom.properties恰好 1 个时pom.xml恰好 1 个时MANIFEST.MF文件名版本兜底versionFromPropertiesFile从 jar 内的version.properties等属性文件取版本。micronaut-aop-4.9.11夹具验证了第 3 优先级其 Manifest 含Implementation-Version: 4.9.11与Implementation-Title: Micronaut CorePomProject提供io.micronaut:micronaut-aop的完整坐标测试用例见 archive_parser_test.go。而uber-app-version-properties、uber-app-version-placeholder、lib-with-version-properties三个夹具则覆盖第 5 优先级uber-app-version-properties/version.properties内容为tagv0.63.5、hash1dca717、date2026-08-05用于验证从非标准属性文件提取版本号的兜底路径。确定性匹配multiple-matching 与 org.multiple-thenamemultiple-matching-2.11.5一个 jar 内存在multiple-matching-1/2/3三个pom.properties与org.multiple-thename存在com.multiple:thename与org.multiple:thename两组坐标两个夹具对应Test_deterministicMatchingPomPropertiesarchive_parser_test.go。该测试对同一夹具连续循环 5 次调用discoverMainPackageFromPomInfo断言每次都能确定性地选出同一个 Maven 坐标如org.multiple:multiple-matching-1:2.11.5、org.multiple:thename:10.11.12防止解析结果在多候选 POM 时出现随机性或非稳定排序。如何为 Syft 新增一个 Java 元数据测试夹具结合上述约定为 Syft 贡献一个新的 Java 元数据测试场景只需四步均在只读查看仓库的前提下参照现有 fixture 复制其模式即可摆目录在 syft/pkg/cataloger/java/testdata/jar-metadata 下新建目录目录名即目标 jar 名如my-lib-1.0.0/内部按真实 jar 布局放置META-INF/MANIFEST.MF与META-INF/maven/groupId/artifactId/{pom.properties,pom.xml}只放被测逻辑需要的元数据注册 Makefile在 Makefile 中按既有 pattern 添加命名常量与$(CACHE_DIR)/name.jar:规则如需.hpi等非常规扩展名参考gradle.hpi规则写测试在 archive_parser_test.go 的Test_parseJavaArchive_regressions测试表中追加用例通过fixtureName指向新夹具并声明期望的pkg.Package含 Name/Version/Type/PURL/Licenses/JavaArchive 元数据与artifact.Relationship测试运行时会通过generateJavaMetadataJarFixture自动make生成缓存 jar验证指纹修改任何夹具源文件后重新生成cache.fingerprint保证缓存失效机制生效提交时不要将cache/下的生成 jar 与.fingerprint文件纳入版本控制它们由clean目标管理。小结jar-metadata/夹具目录是 Syft Java cataloger 测试体系的一个精巧缩影以目录即 zip的约定把二进制 jar 排除在仓库之外用 Makefile 统一管理生成、指纹与清理并用最小化的元数据组合精准覆盖了多 POM 父级解析PR 2231、重复 jar 去重issue #2130、Weave-Classes 排除、Jenkins 插件类型识别、多 POM 确定性匹配、属性文件版本兜底等真实世界回归场景。理解这套夹具约定不仅能读懂 archive_parser_test.go 中每个回归用例的意图也为向 Syft 贡献 Java 解析相关测试提供了可直接套用的标准姿势。【免费下载链接】syftCLI tool and library for generating a Software Bill of Materials from container images and filesystems项目地址: https://gitcode.com/GitHub_Trending/sy/syft创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门