View a markdown version of this page

插件测试指南 - Amazon Inspector

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

插件测试指南

插件作者完全使用 Lua 编写和测试 Lua 插件——不需要 Go 工具链。测试与插件位于同一位置,init_test.lua并通过inspector-sbomgen plugin test命令运行。

有关插件的常规创作,请参阅。插件开发者指南有关完整的函数目录(包括 testing.* API),请参阅插件 API 参考

-- init_test.lua (next to init.lua) function test_discovers_curl_version() local result = testing.scan_directory("_testdata/include/curl") testing.assert_equals(1, #result.findings) testing.assert_equals("libcurl", result.findings[1].name) testing.assert_equals("8.14.1", result.findings[1].version) end
# Run every test under a plugin directory inspector-sbomgen plugin test --path ./my-plugins # Verbose output — show each test name and result inspector-sbomgen plugin test --path ./my-plugins -v

快速入门

1. 创建测试文件

放在你的插件init_test.lua旁边init.lua

my-plugin/ ├── discovery/cross-platform/extra-ecosystems/curl/ │ ├── init.lua │ ├── init_test.lua │ └── _testdata/ │ └── include/curl/curlver.h

2. 编写测试函数

任何以开头的全局函数都会test_被发现并执行:

function test_finds_libcurl() local result = testing.scan_directory("_testdata/include/curl") testing.assert_equals(1, #result.findings) testing.assert_equals("libcurl", result.findings[1].name) end function test_no_findings_for_empty_dir() local result = testing.scan_directory("_testdata/empty") testing.assert_equals(0, #result.findings) end

3. 运行测试

inspector-sbomgen plugin test --path ./my-plugin

目录布局

插件结构

测试文件和测试数据与插件位于同一位置:

my-plugins/ ├── discovery/ │ └── cross-platform/ │ └── extra-ecosystems/ │ └── curl/ │ ├── init.lua # plugin source │ ├── init_test.lua # test file │ └── _testdata/ # test fixtures │ ├── include/curl/curlver.h │ └── binaries/unix/curl ├── collection/ │ └── cross-platform/ │ └── extra-ecosystems/ │ └── curl-installation/ │ ├── init.lua │ └── init_test.lua

测试文件命名

  • 默认:在插件init_test.lua旁边 init.lua

  • 每个插件有多个测试文件:发现任何匹配*_test.lua的文件

  • 示例:init_test.luaparsing_test.luadiscovery_test.lua

测试数据:_testdata/

测试数据位于插件_testdata/旁边。前导下划线是一种约定,它使灯具在视觉上与插件源分开;搜索文件_testdata/时不会使用该plugin test命令,因此永远不会将灯具误认为是测试*_test.lua文件。

测试文件引用带有相对路径的灯具:

local result = testing.scan_directory("_testdata/include/curl")

路径是相对于包含测试文件的目录进行解析的。

testing.* API

扫描功能

每个扫描函数都会创建一个工件,运行插件的发现 → 集合管道,并返回结果。测试作者从不手动创建工件、事件总线或注册表。

-- Scan a directory of test fixtures (most common) local result = testing.scan_directory("_testdata/curl") -- Alias for scan_directory (archives use the same backend) local result = testing.scan_archive("_testdata/app.tar.gz") -- Scan as a localhost artifact local result = testing.scan_localhost("_testdata/curl") -- Scan a compiled binary local result = testing.scan_binary("_testdata/binaries/curl") -- Scan a mounted volume local result = testing.scan_volume("_testdata/volume-root") -- Scan a container image tarball local result = testing.scan_container("_testdata/images/alpine.tar")

每种扫描功能:

  • 为每个调用创建一个新的构件(调用之间没有状态泄漏)

  • 仅加载当前插件的 discovery + 集合对

  • 返回结果表

结果表

result.findings -- array of finding tables result.findings[1].name -- package name (string) result.findings[1].version -- package version (string) result.findings[1].purl -- package URL (string) result.findings[1].component_type -- component type (string) result.findings[1].properties -- table<string, string> result.findings[1].children -- array of nested finding tables (same shape)

断言

-- Equality testing.assert_equals(expected, actual, message?) testing.assert_not_equals(expected, actual, message?) -- Truthiness testing.assert_true(value, message?) testing.assert_false(value, message?) -- Nil checks testing.assert_nil(value, message?) testing.assert_not_nil(value, message?) -- String testing.assert_contains(haystack, needle, message?) testing.assert_matches(string, pattern, message?) -- Tables testing.assert_length(table, expected_length, message?) -- Control flow testing.fail(message) -- immediately fail the current test testing.skip(message) -- skip the current test (not a failure)

标准 sbomgen.* API

完整的 sbomgen.* API(文件 I/O、正则表达式、系统信息、日志等)可在测试文件中使用,与生产插件相同。但是,需要工件的sbomgen.*函数(例如sbomgen.read_file())只能在testing.scan_*回调中起作用——它们在测试函数的顶层不可用。

运行测试

# Run all tests under a plugin directory inspector-sbomgen plugin test --path ./my-plugins # Filter by regex pattern inspector-sbomgen plugin test --path ./my-plugins --run curl # Verbose output (show each test name and result) inspector-sbomgen plugin test --path ./my-plugins -v # Stop on the first failing test inspector-sbomgen plugin test --path ./my-plugins --fail-fast

--path标志接受插件根目录(包含 discovery/ and/or collection/)或单个生态系统目录(自动检测)。如果任何测试失败,该命令将以非零值退出。

输出格式

使用-v,每个测试都会打印一=== RUN行和一条结果行(--- PASS--- FAIL、或--- SKIP)。如果没有-v,则只有失败的测试才会打印。最后会打印一个摘要行:

=== RUN curl/discovery/init_test/test_discovers_libcurl_header --- PASS: curl/discovery/init_test/test_discovers_libcurl_header (0.04s) === RUN curl/discovery/init_test/test_discovers_curl_binary_unix --- PASS: curl/discovery/init_test/test_discovers_curl_binary_unix (0.04s) === RUN curl/discovery/init_test/test_no_findings_for_unrelated_files --- PASS: curl/discovery/init_test/test_no_findings_for_unrelated_files (0.04s) ok 3 tests passed

测试插件助手

测试文件加载到与插件相同的 Lua 虚拟机中。init.lua插件中定义的全局函数可以从测试中调用。要测试辅助函数,请将它们公开为全局变量或在模块表中:

-- init.lua M = {} function M.parse_version(raw) return string.match(raw, "(%d+%.%d+%.%d+)") end function collect(file_path) local ver = M.parse_version(...) -- ... end
-- init_test.lua function test_parse_version_extracts_semver() testing.assert_equals("1.2.3", M.parse_version("curl/1.2.3")) end function test_parse_version_returns_nil_for_garbage() testing.assert_nil(M.parse_version("not-a-version")) end

local中声明init.lua的函数对测试文件不可见。这是标准的 Lua 作用域。

行为和不变量

共享虚拟机,共享状态

单个文件中的测试函数共享一个 Lua VM。在一个函数中设置的全局变量对后续test_*函数可见。如果两个函数定义了同一个全局函数,则第二个函数会覆盖第一个函数。每个测试都应该是独立的,不依赖于其他测试的状态。

不确定的执行顺序

测试函数是通过迭代 Lua 的全局表来发现的,该全局表使用基于哈希的排序。不能保证测试会按照定义的顺序运行。不要编写依赖于执行顺序的测试。

每次扫描调用都会有新的伪像

每次调用testing.scan_directory()(或任何扫描函数)都会创建一个全新的构件。在测试中的扫描调用之间或测试之间没有状态传输。

插件加载

插件每次测试运行加载一次,而不是每个测试文件加载一次。测试运行器从提供的文件系统加载所有插件,然后按阶段、平台、类别和生态系统将每个测试文件与其对应的插件虚拟机进行匹配。

断言行为

当断言失败时,会记录失败,但测试函数会继续执行——后续的断言和语句仍会运行。如果在同一个测试中多个断言失败,则最近的失败是摘要中报告的消息;先前的失败将被覆盖。要在第一次失败时停止测试,请在断言失败后从函数返回(或testing.fail()在条件中使用)。

限制

  • local函数不可测试。只有中的init.lua全局函数对测试文件可见。如果助手需要测试,请通过模块表公开它们。

  • 扫描呼叫 I/O 之外没有sbomgen.*文件。诸如此类的函数sbomgen.read_file()需要一个工件上下文,该上下文仅存在于testing.scan_*调用中。

  • 没有生命周期挂钩。没有before_eachafter_eachsetup、或teardown。每个测试函数都管理自己的状态。

  • 没有测试超时。永久循环的测试函数将使跑步者陷入困境。

  • 没有覆盖率报告。无法衡量行使init.lua了哪些路线。

  • 没有基准。测试框架不支持性能基准。

开发者责任

在编写新的 Lua 插件时

  • 在你的 “init_test.lua旁边创建” init.lua

  • _testdata/使用最少的固定装置进行创作,以锻炼你的插件的逻辑

  • 编写涵盖:成功检测、版本提取、边缘案例和不匹配场景的test_*函数

  • 提交之前在本地运行测试

测试数据指南

  • 尽量减少固定装置——使用练习行为的最小文件

  • 避免将大型二进制文件提交到一个小文本夹具就足够_testdata/的时候

  • 每个插件都_testdata/应该是独立的 —— 不得引用插件目录之外的文件