diff --git a/src/tools/collect-license-metadata/src/spdx.rs b/src/tools/collect-license-metadata/src/spdx.rs
index 11dd72ee2c38..a94f2bcf51ec 100644
--- a/src/tools/collect-license-metadata/src/spdx.rs
+++ b/src/tools/collect-license-metadata/src/spdx.rs
@@ -97,3 +97,6 @@ fn build_file_entry(
.ok_or_else(|| anyhow::anyhow!("file missing FileCopyrightText"))?,
})
}
+
+#[cfg(test)]
+mod tests;
diff --git a/src/tools/collect-license-metadata/src/spdx/tests.rs b/src/tools/collect-license-metadata/src/spdx/tests.rs
new file mode 100644
index 000000000000..5b7cb411931d
--- /dev/null
+++ b/src/tools/collect-license-metadata/src/spdx/tests.rs
@@ -0,0 +1,134 @@
+use super::*;
+
+// Clause 8.1 ("File name field") specifies that each file section begins with
+// a `FileName` tag whose value is a relative path prefixed with "./".
+// Clause 8.5 ("Concluded license") and 8.8 ("Copyright text") give the
+// corresponding per-file fields.
+//
+#[test]
+fn single_file_entry() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: LGPL-2.0-only
+FileCopyrightText: Copyright 2008-2010 John Smith";
+
+ let files = parse_tag_value(input).unwrap();
+ assert_eq!(files.len(), 1);
+ assert_eq!(files[0].name, "./package/foo.c");
+ assert_eq!(files[0].concluded_license, "LGPL-2.0-only");
+ assert_eq!(files[0].copyright_text, "Copyright 2008-2010 John Smith");
+}
+
+// Clause 8.5 shows compound SPDX licence expressions as valid values for
+// `LicenseConcluded`, e.g. "(LGPL-2.0-only OR LicenseRef-2)".
+//
+#[test]
+fn compound_license_expression() {
+ let input = "\
+FileName: ./src/lib.rs
+LicenseConcluded: (LGPL-2.0-only OR LicenseRef-2)
+FileCopyrightText: Copyright Example Company";
+
+ let files = parse_tag_value(input).unwrap();
+ assert_eq!(files.len(), 1);
+ assert_eq!(files[0].concluded_license, "(LGPL-2.0-only OR LicenseRef-2)");
+}
+
+// Clause 8.8 shows the copyright text wrapped in a single-line
+// ... block: e.g.
+// `FileCopyrightText: Copyright 2008-2010 John Smith`
+//
+#[test]
+fn single_line_text_block() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: LGPL-2.0-only
+FileCopyrightText: Copyright 2008-2010 John Smith";
+
+ let files = parse_tag_value(input).unwrap();
+ assert_eq!(files.len(), 1);
+ assert_eq!(files[0].copyright_text, "Copyright 2008-2010 John Smith");
+}
+
+// Clause 6.10 ("Creator comment") demonstrates a multi-line ... block.
+//
+#[test]
+fn multi_line_text_block() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: MIT
+FileCopyrightText: Copyright 2008-2010 John Smith
+Copyright 2019 Jane Doe";
+
+ let files = parse_tag_value(input).unwrap();
+ assert_eq!(files.len(), 1);
+ assert_eq!(files[0].copyright_text, "Copyright 2008-2010 John Smith\nCopyright 2019 Jane Doe");
+}
+
+// Clause 5 ("Composition of an SPDX document") states that a document may
+// contain zero or many File Information sections. Each `FileName` tag starts
+// a new section, so consecutive file blocks must be parsed independently.
+//
+#[test]
+fn multiple_file_entries() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: LGPL-2.0-only
+FileCopyrightText: Copyright 2008-2010 John Smith
+FileName: ./package/bar.c
+LicenseConcluded: MIT
+FileCopyrightText: Copyright Example Company";
+
+ let files = parse_tag_value(input).unwrap();
+ assert_eq!(files.len(), 2);
+
+ assert_eq!(files[0].name, "./package/foo.c");
+ assert_eq!(files[0].concluded_license, "LGPL-2.0-only");
+ assert_eq!(files[0].copyright_text, "Copyright 2008-2010 John Smith");
+
+ assert_eq!(files[1].name, "./package/bar.c");
+ assert_eq!(files[1].concluded_license, "MIT");
+ assert_eq!(files[1].copyright_text, "Copyright Example Company");
+}
+
+// A file section without a `LicenseConcluded` tag is malformed.
+#[test]
+fn missing_license_is_an_error() {
+ let input = "\
+FileName: ./package/foo.c
+FileCopyrightText: Copyright 2008-2010 John Smith";
+
+ assert!(parse_tag_value(input).is_err());
+}
+
+// A file section without a `FileCopyrightText` tag is malformed.
+#[test]
+fn missing_copyright_is_an_error() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: MIT";
+
+ assert!(parse_tag_value(input).is_err());
+}
+
+// A section with an unterminated block (no closing ) is malformed.
+#[test]
+fn unterminated_text_block_is_an_error() {
+ let input = "\
+FileName: ./package/foo.c
+LicenseConcluded: MIT
+FileCopyrightText: Copyright 2008-2010 John Smith";
+
+ assert!(parse_tag_value(input).is_err());
+}
+
+// A document with no `FileName` tags at all should produce an empty result.
+#[test]
+fn empty_document_returns_no_entries() {
+ let input = "\
+SPDXVersion: SPDX-2.3
+DataLicense: CC0-1.0";
+
+ let files = parse_tag_value(input).unwrap();
+ assert!(files.is_empty());
+}