Fork of daniellemaywood.uk/gleam — Wasm codegen work
6.1 kB
199 lines
1use std::path::PathBuf;
2
3use itertools::Itertools;
4use serde::Deserialize;
5
6#[derive(Deserialize)]
7struct CargoDenyConfig {
8 advisories: CargoDenyAdvisoriesConfig,
9 licenses: CargoDenyLicensesConfig,
10}
11
12#[derive(Deserialize)]
13struct CargoDenyLicensesConfig {
14 allow: Vec<String>,
15}
16
17#[derive(Deserialize)]
18struct CargoDenyAdvisoriesConfig {
19 ignore: Vec<CargoDenyIgnoreConfig>,
20}
21
22#[derive(Deserialize)]
23struct CargoDenyIgnoreConfig {
24 id: String,
25 reason: String,
26}
27
28#[test]
29fn licence_files_exist_in_repo() {
30 // deny.toml specified which licences dependencies are permitted to have.
31 let dependency_licences = load_cargo_deny_config().licenses.allow;
32
33 // Each licence must exist in the licences directory.
34 let licences_directory = PathBuf::from("../licences");
35 let licences_missing_licence_file = dependency_licences
36 .iter()
37 .filter(|licence| {
38 let path = licences_directory.join(format!("{licence}.txt"));
39 !std::fs::exists(path).unwrap()
40 })
41 .collect_vec();
42
43 assert!(
44 licences_missing_licence_file.is_empty(),
45 "
46All dependency licences must exist in the licences directory.
47These licences are missing their file:
48
49- {licences_missing_licence_file:?}"
50 );
51}
52
53fn load_cargo_deny_config() -> CargoDenyConfig {
54 let toml = std::fs::read_to_string("../deny.toml").unwrap();
55 let config = toml::from_str(&toml).unwrap();
56 config
57}
58
59#[test]
60fn rust_advisories_ignore_deadlines() {
61 let today = time::OffsetDateTime::now_utc().date();
62 let regex = regex::Regex::new(r"(?m)FIX-DEADLINE: (\d{4})-(\d{2})-(\d{2})").unwrap();
63
64 for ignored in load_cargo_deny_config().advisories.ignore {
65 let id = ignored.id;
66 let reason = ignored.reason;
67 let Some(captures) = regex.captures(&reason) else {
68 panic!(
69 "
70deny.toml advisory missing deadline!
71Add a line to the `reason` property for {id} with this format:
72
73 FIX-DEADLINE: 2026-01-05
74
75{reason:?}
76"
77 )
78 };
79
80 let year = captures[1].parse::<i32>().expect("parse year");
81 let month = captures[2].parse::<u8>().expect("parse month int");
82 let month = month.try_into().expect("parse month");
83 let day = captures[3].parse::<u8>().expect("parse day");
84 let deadline = time::Date::from_calendar_date(year, month, day).expect("construct date");
85
86 assert!(
87 today <= deadline,
88 "
89The deadline for fixing advisory {id} has passed.
90Fix it now, or bump the deadline in deny.toml.
91"
92 )
93 }
94}
95
96#[test]
97fn all_files_have_copyright_notice() {
98 let paths = ignore::WalkBuilder::new("..")
99 .follow_links(false)
100 .git_global(true)
101 .git_ignore(true)
102 .hidden(true)
103 .parents(true)
104 .build()
105 .map(|entry| entry.expect("directory traversal succeeds"))
106 .filter(|entry| {
107 entry
108 .file_type()
109 .map(|type_| type_.is_file())
110 .unwrap_or(false)
111 })
112 .map(ignore::DirEntry::into_path)
113 .filter_map(|path| {
114 let file_name = path.file_name().and_then(|s| s.to_str())?;
115 let extension = path.extension()?;
116
117 match extension.to_str().expect("file extension") {
118 // Test files.
119 "new" => return None,
120 "snap" => return None,
121 "txt" if path.starts_with("../test/") || path.starts_with("../licences/") => {
122 return None;
123 }
124
125 // GitHub templates
126 "md" if path.starts_with("../.github/") => return None,
127
128 // Static assets
129 "gz" => return None,
130 "tar" => return None,
131 "exs" => return None,
132 "dockerfile" => return None,
133 "woff2" => return None,
134 "png" => return None,
135 "css" if file_name.ends_with(".min.css") => return None,
136 "js" if file_name.ends_with(".min.js") => return None,
137
138 // Template files to be added to user projects
139 "ps1" if file_name == "erlang-shipment-entrypoint.ps1" => return None,
140 "sh" if file_name == "erlang-shipment-entrypoint.sh" => return None,
141 "mjs" if file_name == "prelude.mjs" || file_name == "echo.mjs" => return None,
142 "erl" if file_name == "gleam@@main.erl" || file_name == "echo.erl" => return None,
143 "mts" if file_name == "prelude.d.mts" => return None,
144
145 // Generated files
146 "toml" if file_name == "manifest.toml" => return None,
147
148 // Source files
149 "config" if file_name == "rebar.config" => (),
150 "css" => (),
151 "proto" => (),
152 "ts" => (),
153 "erl" => (),
154 "ex" => (),
155 "gleam" => (),
156 "hrl" => (),
157 "js" => (),
158 "md" => (),
159 "mjs" => (),
160 "mts" => (),
161 "ps1" => (),
162 "rs" => (),
163 "sh" => (),
164 "toml" => (),
165
166 // HTML templates
167 "html" if path.starts_with("../compiler-core/templates/") => (),
168
169 _ => panic!("Unexpected file extension for {path:?}"),
170 };
171
172 let text = std::fs::read_to_string(&path).expect("read file");
173 if text.contains("SPDX-License-Identifier: Apache-2.0")
174 && text.contains("SPDX-FileCopyrightText: 20")
175 {
176 // Licence information is present
177 return None;
178 }
179
180 Some(path.to_str().unwrap().to_string())
181 })
182 .join("\n - ");
183
184 if !paths.is_empty() {
185 panic!(
186 "Source files missing SPDX licence information!
187
188Add a comment like this:
189
190 // SPDX-License-Identifier: Apache-2.0
191 // SPDX-FileCopyrightText: 2026 The Gleam contributors
192
193To these files:
194
195- {paths}
196"
197 )
198 }
199}