use std::path::{Path, PathBuf};
pub(crate) fn crates_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates/ferrosintesis always has a parent")
.to_path_buf()
}
pub(crate) fn read(path: &Path) -> String {
std::fs::read_to_string(path).unwrap_or_else(|e| {
panic!(
"licensing oracle cannot read {}: {e}.\n\
These oracles derive the attribution guide from the sibling asset \
manifests, so they only run inside the ferrosintesis workspace.",
path.display()
)
})
}
pub(crate) fn default_sample_crates() -> Vec<String> {
let manifest = read(&crates_dir().join("ferrosintesis").join("Cargo.toml"));
let mut out = Vec::new();
let mut inside = false;
for line in manifest.lines() {
let trimmed = line.trim();
if trimmed.starts_with("embedded-samples") && trimmed.contains('[') {
inside = true;
continue;
}
if inside {
if trimmed.starts_with(']') {
break;
}
if let Some(dep) = quoted(trimmed) {
out.push(dep.trim_start_matches("dep:").to_string());
}
}
}
assert!(
!out.is_empty(),
"could not parse the `embedded-samples` feature list out of \
crates/ferrosintesis/Cargo.toml — the oracle would otherwise pass vacuously"
);
out
}
pub(crate) fn quoted(line: &str) -> Option<&str> {
let rest = line.split_once('"')?.1;
rest.split_once('"').map(|(inner, _)| inner)
}
pub(crate) fn declared_license(krate: &str) -> String {
let manifest = read(&crates_dir().join(krate).join("Cargo.toml"));
for line in manifest.lines() {
let trimmed = line.trim();
if let Some(rest) = trimmed.strip_prefix("license") {
if rest.trim_start().starts_with('=') {
if let Some(value) = quoted(trimmed) {
return value.to_string();
}
}
}
}
panic!("{krate}/Cargo.toml declares no `license` field");
}
mod tests {
use super::*;
const MS_BASIC_SOURCE_REV: &str = "d307a2bd899f15bf650efc3c2891211af5cb78b5";
const MS_BASIC_LICENSE: &str = include_str!(
"upstream_licenses/ms_basic_license_d307a2bd899f15bf650efc3c2891211af5cb78b5.md"
);
const YDP_GRAND_UPSTREAM_CREDITS: &str =
include_str!("upstream_licenses/ydp_grand_freepats_credits_20260731.md");
fn license_terms(expression: &str) -> Vec<&str> {
expression
.split(" AND ")
.map(|term| {
term.trim()
.trim_start_matches('(')
.trim_end_matches(')')
.trim()
})
.filter(|term| !term.is_empty())
.collect()
}
fn declared_attribution_licenses(declared_license: &str) -> Vec<&str> {
let mut licenses: Vec<&str> = license_terms(declared_license)
.into_iter()
.filter(|license| *license != "CC0-1.0")
.collect();
licenses.sort_unstable();
licenses.dedup();
licenses
}
const ATTRIBUTION_LICENSES: &[&str] = &["MIT", "CC-BY-3.0", "CC-BY-4.0"];
fn provenance_attribution_licenses(provenance: &str) -> Vec<&'static str> {
let mut licenses: Vec<&'static str> = ATTRIBUTION_LICENSES
.iter()
.copied()
.filter(|license| names_license_id(provenance, license))
.collect();
licenses.sort_unstable();
licenses.dedup();
licenses
}
fn attribution_claim_agrees(declared_license: &str, provenance: &str) -> bool {
declared_attribution_licenses(declared_license)
== provenance_attribution_licenses(provenance)
}
fn format_licenses(licenses: &[&str]) -> String {
if licenses.is_empty() {
"none".to_string()
} else {
licenses.join(", ")
}
}
fn crate_requires_attribution(krate: &str, declared_license: &str) -> bool {
let provenance = read(&crates_dir().join(krate).join("PROVENANCE.md"));
let declared_licenses = declared_attribution_licenses(declared_license);
let provenance_licenses = provenance_attribution_licenses(&provenance);
assert!(
declared_licenses == provenance_licenses,
"{krate}/Cargo.toml declares {declared_license}, whose attribution licence \
ids are {}, but {krate}/PROVENANCE.md names {}. The manifest cannot \
exempt, understate, or overstate its own audio obligations; reconcile \
the declaration with the retained provenance evidence.",
format_licenses(&declared_licenses),
format_licenses(&provenance_licenses)
);
!provenance_licenses.is_empty()
}
fn license_spellings(license_id: &str) -> Vec<String> {
let mut out = vec![license_id.to_string()];
if license_id.starts_with("CC-BY-") {
out.push(license_id.replace('-', " "));
}
out
}
fn contains_word(text: &str, needle: &str) -> bool {
let boundary = |c: Option<char>| !matches!(c, Some(c) if c.is_ascii_alphanumeric());
let mut from = 0;
while let Some(i) = text[from..].find(needle) {
let at = from + i;
let before = text[..at].chars().next_back();
let after = text[at + needle.len()..].chars().next();
if boundary(before) && boundary(after) {
return true;
}
from = at + needle.len();
}
false
}
fn names_license_id(text: &str, license_id: &str) -> bool {
license_spellings(license_id)
.iter()
.any(|s| contains_word(text, s))
}
fn names_license(text: &str, license: &str) -> bool {
let terms = license_terms(license);
!terms.is_empty()
&& terms
.into_iter()
.all(|license_id| names_license_id(text, license_id))
}
fn carries_licensor_owned_signal(notice: &str) -> bool {
let has_source_url = notice.split_whitespace().any(|word| {
word.find("http")
.map(|i| word[i..].len() > 12 && !word.contains("creativecommons.org"))
.unwrap_or(false)
});
let has_copyright_line = notice.lines().any(|line| {
let lower = line.to_ascii_lowercase();
line.contains('©') || (lower.contains("copyright") && lower.contains("(c)"))
});
has_source_url || has_copyright_line
}
fn normalized_credit(text: &str) -> String {
let separated: String = text
.to_ascii_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { ' ' })
.collect();
separated.split_whitespace().collect::<Vec<_>>().join(" ")
}
fn required_distributed_credits(evidence: &str) -> Vec<&str> {
let required: Vec<&str> = evidence
.lines()
.filter_map(|line| line.strip_prefix("- Required distributed credit: `"))
.filter_map(|credit| credit.strip_suffix('`'))
.collect();
assert!(
!required.is_empty(),
"retained upstream credit evidence contains no required distributed credits"
);
required
}
fn missing_required_credits<'a>(evidence: &'a str, document: &str) -> Vec<&'a str> {
let normalized_document = normalized_credit(document);
required_distributed_credits(evidence)
.into_iter()
.filter(|credit| !normalized_document.contains(&normalized_credit(credit)))
.collect()
}
#[test]
fn ydp_credit_oracle_rejects_notice_missing_krishtal() {
let gutted = r#"
"YDP Grand Piano" SoundFont by roberto@zenvoid.org for FreePats.
Underlying computer-performed samples by the Zenph Studios team,
recorded for OLPC.
Source: https://freepats.zenvoid.org/Piano/acoustic-grand-piano.html
"#;
let missing = missing_required_credits(YDP_GRAND_UPSTREAM_CREDITS, gutted);
assert!(
missing.contains(&"Dr. Mikhail Krishtal"),
"the retained upstream evidence must reject a notice that keeps Roberto, \
the work title, the source URL, and the recording context but removes \
Dr. Mikhail Krishtal; missing={missing:?}"
);
}
#[test]
fn ydp_documents_reproduce_retained_upstream_credits() {
let ydp = crates_dir().join("ferrosintesis-samples-ydp-grand");
let parent = crates_dir().join("ferrosintesis");
let documents = [
(
"ferrosintesis-samples-ydp-grand/NOTICE",
read(&ydp.join("NOTICE")),
),
(
"ferrosintesis-samples-ydp-grand/PROVENANCE.md",
read(&ydp.join("PROVENANCE.md")),
),
("ferrosintesis/NOTICE", read(&parent.join("NOTICE"))),
("ferrosintesis/README.md", read(&parent.join("README.md"))),
];
let incomplete: Vec<String> = documents
.into_iter()
.filter_map(|(name, text)| {
let missing = missing_required_credits(YDP_GRAND_UPSTREAM_CREDITS, &text);
(!missing.is_empty()).then(|| format!("{name}: {}", missing.join(", ")))
})
.collect();
assert!(
incomplete.is_empty(),
"published YDP credit documents omit identity or role fragments retained \
from the independent FreePats source record:\n {}",
incomplete.join("\n ")
);
}
fn ms_basic_required_acknowledgements() -> Vec<(&'static str, String)> {
let obligation = "The acknowledgements and copyright notices above must be included";
let before_obligation = MS_BASIC_LICENSE.split_once(obligation).unwrap_or_else(|| {
panic!(
"the committed MS Basic licence fixture no longer carries the redistribution \
obligation marker"
)
});
let required: Vec<(&str, String)> = before_obligation
.0
.lines()
.map(str::trim)
.filter(|line| line.contains("Copyright (c)"))
.map(|line| (line, normalized_credit(line)))
.collect();
assert!(
required.len() >= 5,
"the committed MS Basic licence fixture yielded only {} acknowledgement \
line(s); the parser is probably too narrow",
required.len()
);
required
}
fn ms_basic_sample_crates() -> Vec<String> {
let crates: Vec<String> = default_sample_crates()
.into_iter()
.filter(|krate| {
let provenance = read(&crates_dir().join(krate).join("PROVENANCE.md"));
provenance.contains(MS_BASIC_SOURCE_REV)
&& provenance.contains("MS Basic.sf3")
&& provenance.contains("SHA-256")
})
.collect();
assert!(
!crates.is_empty(),
"no default sample crate provenance names the pinned MS Basic SF3 source"
);
crates
}
fn missing_ms_basic_acknowledgements(document: &str, text: &str) -> Vec<String> {
let haystack = normalized_credit(text);
ms_basic_required_acknowledgements()
.into_iter()
.filter_map(|(raw, normalized)| {
(!haystack.contains(&normalized)).then(|| format!("{document}: {raw}"))
})
.collect()
}
#[test]
fn licence_boilerplate_alone_is_not_an_attribution() {
let gutted = "MIT License\n\nPermission is hereby granted, free of charge, to any \
person obtaining a copy of this software and associated documentation \
files (the \"Software\"), to deal in the Software without \
restriction.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY \
OF ANY KIND.";
assert!(
!carries_licensor_owned_signal(gutted),
"bare licence boilerplate must not read as an attribution"
);
assert!(carries_licensor_owned_signal(
"Copyright (c) 2000-2002, 2008 Frank Wen <getfrank@gmail.com>"
));
assert!(carries_licensor_owned_signal("© 2014-16 Michael Cowgill"));
assert!(carries_licensor_owned_signal(
"Source: https://freesound.org/people/tim.kahn/packs/3957/"
));
assert!(!carries_licensor_owned_signal(
"Licensed CC BY 4.0, see https://creativecommons.org/licenses/by/4.0/"
));
}
#[test]
fn ms_basic_notices_reproduce_every_required_upstream_acknowledgement() {
let mut missing = Vec::new();
for krate in ms_basic_sample_crates() {
missing.extend(missing_ms_basic_acknowledgements(
&format!("{krate}/NOTICE"),
&read(&crates_dir().join(&krate).join("NOTICE")),
));
}
missing.extend(missing_ms_basic_acknowledgements(
"ferrosintesis/NOTICE",
&read(&crates_dir().join("ferrosintesis").join("NOTICE")),
));
assert!(
missing.is_empty(),
"MS-Basic-derived sample notices omit required upstream acknowledgement \
line(s):\n {}",
missing.join("\n ")
);
}
#[test]
fn a_licence_id_inside_a_longer_word_does_not_name_that_licence() {
assert!(names_license("distributed under the MIT License", "MIT"));
assert!(names_license("**CC BY 4.0**, irrevocable", "CC-BY-4.0"));
assert!(names_license("licensed CC-BY-3.0.", "CC-BY-3.0"));
assert!(!names_license(
"including without LIMITATION the rights to use",
"MIT"
));
assert!(!names_license("PERMITTED USES ARE LIMITED", "MIT"));
}
fn credit_tokens(notice: &str, krate: &str, license: &str) -> Vec<String> {
let mut out = Vec::new();
let mut rest = notice;
while let Some((_, after)) = rest.split_once('"') {
match after.split_once('"') {
Some((inner, tail)) => {
let title = inner.trim();
if title.len() >= 4 && !title.contains('\n') {
out.push(title.to_string());
}
rest = tail;
}
None => break,
}
}
for word in notice.split_whitespace() {
if let Some(i) = word.find("http") {
let url = word[i..].trim_end_matches([')', ',', '.', ';', '—']);
if url.len() > 12 && !url.contains("creativecommons.org") {
out.push(url.to_string());
}
}
}
let krate = krate.to_ascii_lowercase();
let license_words: Vec<String> = license_terms(license)
.into_iter()
.flat_map(license_spellings)
.map(|s| s.to_ascii_lowercase())
.collect();
out.retain(|candidate| {
let candidate = candidate.to_ascii_lowercase();
!krate.contains(&candidate)
&& !"ferrosintesis".contains(&candidate)
&& !license_words.iter().any(|s| s.contains(&candidate))
});
out
}
#[test]
fn a_crates_own_name_is_not_a_credit_token() {
let gutted =
"ferrosintesis-samples-ccby audio is licensed CC BY 4.0. See the \"ccby\" bank.";
assert!(
credit_tokens(gutted, "ferrosintesis-samples-ccby", "CC-BY-4.0").is_empty(),
"our own crate name cannot stand in for a licensor, work title, or source URL"
);
}
#[test]
fn provenance_prevents_a_manifest_from_self_exempting() {
let provenance = "Two real recordings from Freesound, each licensed under **CC BY 4.0**.";
assert!(
!attribution_claim_agrees("CC0-1.0", provenance),
"a CC0 manifest declaration must disagree with provenance that records CC BY audio"
);
}
#[test]
fn sax_manifest_declares_both_attribution_layers() {
assert_eq!(
declared_license("ferrosintesis-samples-sax"),
"CC-BY-4.0 AND CC-BY-3.0",
"the sax package embeds the CC BY 4.0 MTG.SoloSax derivative and the \
underlying CC BY 3.0 good-sounds recordings"
);
}
#[test]
fn compound_licence_expression_requires_every_and_operand() {
let declared = "CC-BY-4.0 AND CC-BY-3.0";
assert!(names_license(
"MTG.SoloSax is CC BY 4.0; the underlying good-sounds packs are CC BY 3.0.",
declared
));
assert!(
!names_license("MTG.SoloSax is CC BY 4.0.", declared),
"omitting the underlying CC BY 3.0 layer must not satisfy the declaration"
);
assert!(
!names_license("The good-sounds packs are CC BY 3.0.", declared),
"omitting the derivative CC BY 4.0 layer must not satisfy the declaration"
);
assert!(
!attribution_claim_agrees(
declared,
"MTG.SoloSax derivative files are licensed CC BY 4.0."
),
"provenance that omits CC BY 3.0 must disagree with a compound declaration"
);
assert!(
!attribution_claim_agrees(declared, "The underlying recordings are CC BY 3.0."),
"provenance that omits CC BY 4.0 must disagree with a compound declaration"
);
}
fn notice_section_for(notice: &str, krate: &str) -> Option<String> {
let lines: Vec<&str> = notice.lines().collect();
let is_rule = |l: &str| l.len() >= 20 && l.chars().all(|c| c == '-');
let mut section: Option<String> = None;
for (i, line) in lines.iter().enumerate() {
if is_rule(line) && i + 2 < lines.len() && is_rule(lines[i + 2]) {
section = Some(lines[i + 1].trim().to_string());
}
if mentions(line, krate) {
return section;
}
}
None
}
fn mentions(text: &str, krate: &str) -> bool {
let mut from = 0;
while let Some(i) = text[from..].find(krate) {
let at = from + i;
let after = text[at + krate.len()..].chars().next();
if !matches!(after, Some(c) if c.is_alphanumeric() || c == '-') {
return true;
}
from = at + krate.len();
}
false
}
#[test]
fn readme_names_every_attribution_bearing_sample_bank() {
let readme = read(&crates_dir().join("ferrosintesis").join("README.md"));
let mut missing = Vec::new();
let mut unlicensed = Vec::new();
let mut uncredited = Vec::new();
let mut covered = 0usize;
for krate in default_sample_crates() {
let license = declared_license(&krate);
if !crate_requires_attribution(&krate, &license) {
continue;
}
let Some(row) = readme.lines().find(|l| mentions(l, &krate)) else {
missing.push(format!("{krate} ({license})"));
continue;
};
covered += 1;
if !names_license(row, &license) {
unlicensed.push(format!("{krate}: declares {license}, row says none"));
}
let tokens = credit_tokens(
&read(&crates_dir().join(&krate).join("NOTICE")),
&krate,
&license,
);
assert!(
!tokens.is_empty(),
"{krate}/NOTICE carries no quoted work title and no source URL, so \
there is nothing distinctive to check travelled — that notice cannot \
be a real attribution"
);
if !tokens.iter().any(|t| row.contains(t.as_str())) {
uncredited.push(format!("{krate}: none of {tokens:?} appear in its row"));
}
}
assert!(
missing.is_empty(),
"crates/ferrosintesis/README.md omits {} attribution-bearing sample \
bank(s) that the default build embeds:\n {}\n\n\
A downstream distributor following that guide would ship without the \
required credit. Add each bank, its authors and its licence to the \
\"Sample provenance and licensing\" section.",
missing.len(),
missing.join("\n ")
);
assert!(
unlicensed.is_empty(),
"{} README row(s) name a bank without stating which licence applies, so a \
distributor cannot tell what obligation they are under:\n {}",
unlicensed.len(),
unlicensed.join("\n ")
);
assert!(
uncredited.is_empty(),
"{} README row(s) name a bank but carry none of the credit its own NOTICE \
requires — a row of bare crate names is not an attribution:\n {}",
uncredited.len(),
uncredited.join("\n ")
);
assert!(
covered > 0,
"no attribution-bearing bank was found at all — the oracle is not \
actually checking anything"
);
}
#[test]
fn parent_notice_is_packaged_and_names_every_attribution_bearing_bank() {
let root = crates_dir().join("ferrosintesis");
let notice_path = root.join("NOTICE");
assert!(
notice_path.is_file(),
"crates/ferrosintesis/NOTICE is missing. A default build embeds \
attribution-bearing audio, so the crate must carry a consolidated notice \
telling a binary distributor what to reproduce."
);
let manifest = read(&root.join("Cargo.toml"));
let include = manifest
.lines()
.find(|l| l.trim_start().starts_with("include"))
.unwrap_or_else(|| {
panic!("crates/ferrosintesis/Cargo.toml declares no `include` list")
});
assert!(
include.contains("NOTICE"),
"crates/ferrosintesis/NOTICE exists but the `include` list does not package \
it, so the published crate would ship without it:\n {include}"
);
let notice = read(¬ice_path);
let mut missing = Vec::new();
let mut misfiled = Vec::new();
let mut uncredited = Vec::new();
for krate in default_sample_crates() {
let license = declared_license(&krate);
if !crate_requires_attribution(&krate, &license) {
continue;
}
if !mentions(¬ice, &krate) {
missing.push(format!("{krate} ({license})"));
continue;
}
match notice_section_for(¬ice, &krate) {
Some(section) if names_license(§ion, &license) => {}
Some(section) => misfiled.push(format!(
"{krate} declares {license} but is listed under \"{section}\""
)),
None => misfiled.push(format!(
"{krate} is named outside any licence section, so the notice never \
says what applies to it"
)),
}
let tokens = credit_tokens(
&read(&crates_dir().join(&krate).join("NOTICE")),
&krate,
&license,
);
if !tokens.iter().any(|t| notice.contains(t.as_str())) {
uncredited.push(format!("{krate}: none of {tokens:?} appear"));
}
}
assert!(
missing.is_empty(),
"crates/ferrosintesis/NOTICE omits {} attribution-bearing bank(s):\n {}",
missing.len(),
missing.join("\n ")
);
assert!(
misfiled.is_empty(),
"{} bank(s) are named in crates/ferrosintesis/NOTICE under a licence heading \
that is not their own:\n {}",
misfiled.len(),
misfiled.join("\n ")
);
assert!(
uncredited.is_empty(),
"{} bank(s) are named in crates/ferrosintesis/NOTICE but none of the credit \
from their own NOTICE travelled with them — a list of crate names satisfies \
no licence:\n {}",
uncredited.len(),
uncredited.join("\n ")
);
}
#[test]
fn every_attribution_bearing_sample_bank_ships_a_notice() {
let mut missing = Vec::new();
for krate in default_sample_crates() {
let license = declared_license(&krate);
if !crate_requires_attribution(&krate, &license) {
continue;
}
let notice = crates_dir().join(&krate).join("NOTICE");
if !notice.is_file() {
missing.push(format!("{krate} ({license})"));
continue;
}
let text = read(¬ice);
assert!(
text.trim().len() > 40,
"{krate}/NOTICE exists but is too short to carry a real attribution"
);
assert!(
!credit_tokens(&text, &krate, &license).is_empty(),
"{krate}/NOTICE names no work and cites no source (no quoted title, no \
URL) — it is text, not an attribution"
);
assert!(
names_license(&text, &license),
"{krate}/NOTICE never states the {license} licence it is reproducing, so \
a distributor cannot tell what the obligation is"
);
assert!(
carries_licensor_owned_signal(&text),
"{krate}/NOTICE carries no source URL and no `Copyright (c) …` line, so \
nothing in it identifies a licensor.\nA quoted phrase is not enough on \
its own: strip this crate's NOTICE down to bare MIT boilerplate and the \
quoted tokens \"Software\" and \"AS IS\" survive, so the credit check \
passes while every real credit has been deleted (MM-BUG-KILN-00115). A \
URL and a copyright line are the licensor's, not the licence text's."
);
assert!(
notice_packaged(&krate),
"{krate} has a NOTICE but its Cargo.toml `include` does not package it, \
so the published crate would ship without the required credit"
);
}
assert!(
missing.is_empty(),
"sample bank(s) require attribution but ship no NOTICE:\n {}",
missing.join("\n ")
);
}
fn notice_packaged(krate: &str) -> bool {
let manifest = read(&crates_dir().join(krate).join("Cargo.toml"));
manifest
.lines()
.find(|l| l.trim_start().starts_with("include"))
.is_some_and(|l| l.contains("NOTICE"))
}
fn packaged_bank_documents(krate: &str) -> Vec<(String, String)> {
let root = crates_dir().join(krate);
manifest_include_items(krate)
.into_iter()
.filter(|item| !item.contains('*'))
.filter(|item| {
let lower = item.to_ascii_lowercase();
lower == "notice"
|| lower.ends_with(".md")
|| lower.contains("copying")
|| lower.contains("license")
})
.filter_map(|item| {
let path = root.join(&item);
path.is_file().then(|| (item, read(&path)))
})
.collect()
}
fn packaged_documents_text(docs: &[(String, String)]) -> String {
docs.iter()
.map(|(name, text)| format!("--- {name} ---\n{text}"))
.collect::<Vec<_>>()
.join("\n")
}
fn manifest_include_items(krate: &str) -> Vec<String> {
let manifest = read(&crates_dir().join(krate).join("Cargo.toml"));
let mut include = String::new();
let mut inside_include = false;
for line in manifest.lines() {
let trimmed = line.trim_start();
if !inside_include && trimmed.starts_with("include") {
inside_include = true;
}
if inside_include {
include.push_str(line);
include.push('\n');
if line.contains(']') {
break;
}
}
}
assert!(
!include.is_empty(),
"{krate}/Cargo.toml declares no `include` list, so the licensing oracle \
cannot tell which upstream documents the published crate packages"
);
let mut out = Vec::new();
let mut rest = include.as_str();
while let Some((_, after_open)) = rest.split_once('"') {
match after_open.split_once('"') {
Some((item, after_close)) => {
out.push(item.to_string());
rest = after_close;
}
None => break,
}
}
assert!(
!out.is_empty(),
"{krate}/Cargo.toml has an `include` list but the oracle parsed no entries:\n\
{include}"
);
out
}
fn cc_by_canonical_uri(license_id: &str) -> Option<&'static str> {
match license_id {
"CC-BY-3.0" => Some("https://creativecommons.org/licenses/by/3.0/"),
"CC-BY-4.0" => Some("https://creativecommons.org/licenses/by/4.0/"),
_ => None,
}
}
fn cc_by_legal_text_marker(license_id: &str) -> Option<&'static str> {
match license_id {
"CC-BY-3.0" => Some("Creative Commons Attribution 3.0 Unported"),
"CC-BY-4.0" => Some("Creative Commons Attribution 4.0 International Public License"),
_ => None,
}
}
fn missing_cc_by_license_documents<'a>(docs: &str, declared_license: &'a str) -> Vec<&'a str> {
license_terms(declared_license)
.into_iter()
.filter(|license_id| license_id.starts_with("CC-BY-"))
.filter(|license_id| {
let uri_present =
cc_by_canonical_uri(license_id).is_some_and(|uri| docs.contains(uri));
let legal_text_present =
cc_by_legal_text_marker(license_id).is_some_and(|marker| docs.contains(marker));
!(uri_present || legal_text_present)
})
.collect()
}
#[test]
fn cc_by_sample_banks_package_license_uri_or_text() {
let mut incomplete = Vec::new();
for krate in default_sample_crates() {
let license = declared_license(&krate);
if !license_terms(&license)
.into_iter()
.any(|license_id| license_id.starts_with("CC-BY-"))
{
continue;
}
let docs = packaged_bank_documents(&krate);
assert!(
!docs.is_empty(),
"{krate} declares {license} audio but packages no README/NOTICE/COPYING/LICENSE \
document carrying the upstream terms"
);
let missing =
missing_cc_by_license_documents(&packaged_documents_text(&docs), &license);
if !missing.is_empty() {
incomplete.push(format!("{krate}: {}", missing.join(", ")));
}
}
assert!(
incomplete.is_empty(),
"CC BY sample bank package(s) omit the licence URI or legal text required by \
their declared licence:\n {}",
incomplete.join("\n ")
);
}
#[test]
fn sax_cc_by_3_uri_is_required_independently_of_cc_by_4_uri() {
let krate = "ferrosintesis-samples-sax";
let license = declared_license(krate);
let docs = packaged_documents_text(&packaged_bank_documents(krate));
assert!(
missing_cc_by_license_documents(&docs, &license).is_empty(),
"the checked-in sax package must carry every declared CC BY licence URI or text"
);
let without_cc_by_3_uri = docs.replace("https://creativecommons.org/licenses/by/3.0/", "");
assert_eq!(
missing_cc_by_license_documents(&without_cc_by_3_uri, &license),
vec!["CC-BY-3.0"],
"a CC BY 4.0 URI must not satisfy the sax package's independent CC BY 3.0 layer"
);
}
fn missing_mit_notice_requirements(docs: &str, provenance: &str) -> Vec<&'static str> {
let mut missing = Vec::new();
let required_mit_fragments = [
(
"MIT permission grant",
"Permission is hereby granted, free of charge",
),
(
"MIT permission-notice inclusion condition",
"The above copyright notice and this permission notice shall be included",
),
(
"MIT warranty disclaimer",
"THE SOFTWARE IS PROVIDED \"AS IS\"",
),
];
for (name, fragment) in required_mit_fragments {
if !docs.contains(fragment) {
missing.push(name);
}
}
if provenance.contains("MuseScore") || provenance.contains("FluidR3") {
let required_musescore_fragments = [
(
"FluidR3 original copyright",
"FluidR3 (original version) by Frank Wen Copyright (c) 2000-02",
),
(
"FluidR3Mono conversion copyright",
"Mono conversion (FluidR3Mono) by Michael Cowgill Copyright (c) 2014-17",
),
(
"MuseScore_General adaptation copyright",
"Adaptation for MuseScore_General.sf2 by S. Christian Collins Copyright (c) 2018-19",
),
(
"Temple Blocks copyright",
"Temple Blocks instrument provided by Ethan Winer Copyright (c) 2002",
),
(
"Drumline Cymbals copyright",
"Drumline Cymbals provided by Michael Schorsch Copyright (c) 2016",
),
(
"FluidR3Mono copyright notice",
"Copyright (c) 2014-16 Michael Cowgill",
),
(
"FluidR3 copyright notice",
"Copyright (c) 2000-2002, 2008 Frank Wen <getfrank@gmail.com>",
),
];
for (name, fragment) in required_musescore_fragments {
if !docs.contains(fragment) {
missing.push(name);
}
}
}
missing
}
#[test]
fn mit_sample_banks_package_permission_grant_and_required_copyrights() {
let mut incomplete = Vec::new();
for krate in default_sample_crates() {
let license = declared_license(&krate);
if license != "MIT" {
continue;
}
let docs = packaged_bank_documents(&krate);
assert!(
!docs.is_empty(),
"{krate} declares MIT audio but packages no README/NOTICE/COPYING/LICENSE \
document carrying the upstream terms"
);
let packaged_text = packaged_documents_text(&docs);
let provenance = read(&crates_dir().join(&krate).join("PROVENANCE.md"));
let missing = missing_mit_notice_requirements(&packaged_text, &provenance);
if !missing.is_empty() {
incomplete.push(format!("{krate}: {}", missing.join(", ")));
}
}
assert!(
incomplete.is_empty(),
"MIT sample bank package(s) omit required upstream notice text:\n {}",
incomplete.join("\n ")
);
}
}