Fork of daniellemaywood.uk/gleam — Wasm codegen work
13 kB
433 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4use ecow::{EcoString, eco_format};
5use hexpm::version::Version;
6
7use super::*;
8use crate::{
9 Warning,
10 build::SourceFingerprint,
11 io::{FileSystemWriter, memory::InMemoryFileSystem},
12 line_numbers,
13 parse::extra::ModuleExtra,
14 warning::NullWarningEmitterIO,
15};
16
17use std::time::Duration;
18
19#[derive(Debug)]
20struct LoaderTestOutput {
21 to_compile: Vec<EcoString>,
22 cached: Vec<EcoString>,
23 warnings: Vec<Warning>,
24}
25
26const TEST_SOURCE_1: &'static str = "const x = 1";
27const TEST_SOURCE_2: &'static str = "const x = 2";
28
29fn write_src(fs: &InMemoryFileSystem, path: &str, seconds: u64, src: &str) {
30 let path = Utf8Path::new(path);
31 fs.write(&path, src).unwrap();
32 fs.set_modification_time(&path, SystemTime::UNIX_EPOCH + Duration::from_secs(seconds));
33}
34
35fn write_cache(
36 fs: &InMemoryFileSystem,
37 name: &str,
38 seconds: u64,
39 deps: Vec<(EcoString, SrcSpan)>,
40 src: &str,
41) {
42 let line_numbers = line_numbers::LineNumbers::new(src);
43 let mtime = SystemTime::UNIX_EPOCH + Duration::from_secs(seconds);
44 let cache_metadata = CacheMetadata {
45 mtime,
46 codegen_performed: true,
47 dependencies: deps,
48 fingerprint: SourceFingerprint::new(src),
49 line_numbers: line_numbers.clone(),
50 };
51
52 let artefact_name = name.replace("/", "@");
53 let path = Utf8Path::new("/artefact").join(format!("{artefact_name}.cache_meta"));
54 fs.write_bytes(&path, &cache_metadata.to_binary()).unwrap();
55
56 let cache = crate::type_::ModuleInterface {
57 name: name.into(),
58 origin: Origin::Src,
59 package: "my_package".into(),
60 types: Default::default(),
61 types_value_constructors: Default::default(),
62 values: Default::default(),
63 accessors: Default::default(),
64 line_numbers: line_numbers.clone(),
65 is_internal: false,
66 src_path: Utf8PathBuf::from(format!("/src/{}.gleam", name)),
67 warnings: vec![],
68 minimum_required_version: Version::new(0, 1, 0),
69 type_aliases: Default::default(),
70 documentation: Default::default(),
71 contains_echo: false,
72 references: Default::default(),
73 inline_functions: Default::default(),
74 };
75 let path = Utf8Path::new("/artefact").join(format!("{artefact_name}.cache"));
76 fs.write_bytes(&path, &metadata::encode(&cache).unwrap())
77 .unwrap();
78}
79
80fn run_loader(fs: InMemoryFileSystem, root: &Utf8Path, artefact: &Utf8Path) -> LoaderTestOutput {
81 let mut defined = im::HashMap::new();
82 let ids = UniqueIdGenerator::new();
83 let (emitter, warnings) = WarningEmitter::vector();
84
85 let loader = PackageLoader {
86 io: fs.clone(),
87 ids,
88 mode: Mode::Dev,
89 paths: ProjectPaths::new(root.into()),
90 warnings: &emitter,
91 codegen: CodegenRequired::Yes,
92 artefact_directory: &artefact,
93 package_name: &"my_package".into(),
94 target: Target::JavaScript,
95 stale_modules: &mut StaleTracker::default(),
96 already_defined_modules: &mut defined,
97 incomplete_modules: &mut HashSet::new(),
98 cached_warnings: CachedWarnings::Ignore,
99 };
100 let loaded = loader.run().unwrap();
101
102 LoaderTestOutput {
103 to_compile: loaded.to_compile.into_iter().map(|m| m.name).collect(),
104 cached: loaded.cached.into_iter().map(|m| m.name).collect(),
105 warnings: warnings.take(),
106 }
107}
108
109#[test]
110fn no_modules() {
111 let fs = InMemoryFileSystem::new();
112 let root = Utf8Path::new("/");
113 let artefact = Utf8Path::new("/artefact");
114
115 let loaded = run_loader(fs, root, artefact);
116 assert!(loaded.to_compile.is_empty());
117 assert!(loaded.cached.is_empty());
118}
119
120#[test]
121fn one_src_module() {
122 let fs = InMemoryFileSystem::new();
123 let root = Utf8Path::new("/");
124 let artefact = Utf8Path::new("/artefact");
125
126 write_src(&fs, "/src/main.gleam", 0, "const x = 1");
127
128 let loaded = run_loader(fs, root, artefact);
129 assert_eq!(loaded.to_compile, vec![EcoString::from("main")]);
130 assert!(loaded.cached.is_empty());
131}
132
133#[test]
134fn one_test_module() {
135 let fs = InMemoryFileSystem::new();
136 let root = Utf8Path::new("/");
137 let artefact = Utf8Path::new("/artefact");
138
139 write_src(&fs, "/test/main.gleam", 0, "const x = 1");
140
141 let loaded = run_loader(fs, root, artefact);
142 assert_eq!(loaded.to_compile, vec![EcoString::from("main")]);
143 assert!(loaded.cached.is_empty());
144}
145
146#[test]
147fn one_dev_module() {
148 let fs = InMemoryFileSystem::new();
149 let root = Utf8Path::new("/");
150 let artefact = Utf8Path::new("/artefact");
151
152 write_src(&fs, "/dev/main.gleam", 0, "const x = 1");
153
154 let loaded = run_loader(fs, root, artefact);
155 assert_eq!(loaded.to_compile, vec![EcoString::from("main")]);
156 assert!(loaded.cached.is_empty());
157}
158
159#[test]
160fn importing() {
161 let fs = InMemoryFileSystem::new();
162 let root = Utf8Path::new("/");
163 let artefact = Utf8Path::new("/artefact");
164
165 write_src(&fs, "/src/three.gleam", 0, "import two");
166 write_src(&fs, "/src/one.gleam", 0, "");
167 write_src(&fs, "/src/two.gleam", 0, "import one");
168
169 let loaded = run_loader(fs, root, artefact);
170 assert_eq!(
171 loaded.to_compile,
172 vec![
173 EcoString::from("one"),
174 EcoString::from("two"),
175 EcoString::from("three")
176 ]
177 );
178 assert!(loaded.cached.is_empty());
179}
180
181#[test]
182fn reading_cache() {
183 let fs = InMemoryFileSystem::new();
184 let root = Utf8Path::new("/");
185 let artefact = Utf8Path::new("/artefact");
186
187 write_src(&fs, "/src/one.gleam", 0, TEST_SOURCE_1);
188 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1);
189
190 let loaded = run_loader(fs, root, artefact);
191 assert!(loaded.to_compile.is_empty());
192 assert_eq!(loaded.cached, vec![EcoString::from("one")]);
193}
194
195#[test]
196fn module_is_stale_if_cache_older() {
197 let fs = InMemoryFileSystem::new();
198 let root = Utf8Path::new("/");
199 let artefact = Utf8Path::new("/artefact");
200
201 write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2);
202 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1);
203
204 let loaded = run_loader(fs, root, artefact);
205 assert_eq!(loaded.to_compile, vec![EcoString::from("one")]);
206 assert!(loaded.cached.is_empty());
207}
208
209#[test]
210fn module_is_stale_if_deps_are_stale() {
211 let fs = InMemoryFileSystem::new();
212 let root = Utf8Path::new("/");
213 let artefact = Utf8Path::new("/artefact");
214
215 // Cache is stale
216 write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2);
217 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1);
218
219 // Cache is fresh but dep is stale
220 write_src(&fs, "/src/two.gleam", 1, "import one");
221 write_cache(
222 &fs,
223 "two",
224 2,
225 vec![(EcoString::from("one"), SrcSpan { start: 0, end: 0 })],
226 "import one",
227 );
228
229 // Cache is fresh
230 write_src(&fs, "/src/three.gleam", 1, TEST_SOURCE_1);
231 write_cache(&fs, "three", 2, vec![], TEST_SOURCE_1);
232
233 let loaded = run_loader(fs, root, artefact);
234 assert_eq!(
235 loaded.to_compile,
236 vec![EcoString::from("one"), EcoString::from("two")]
237 );
238 assert_eq!(loaded.cached, vec![EcoString::from("three")]);
239}
240
241#[test]
242fn module_is_stale_if_deps_removed() {
243 let fs = InMemoryFileSystem::new();
244 let root = Utf8Path::new("/");
245 let artefact = Utf8Path::new("/artefact");
246
247 // Source is removed, cache is present
248 write_cache(&fs, "nested/one", 0, vec![], TEST_SOURCE_1);
249
250 // Cache is fresh but dep is removed
251 write_src(&fs, "/src/two.gleam", 1, "import one");
252 write_cache(
253 &fs,
254 "two",
255 2,
256 vec![(EcoString::from("nested/one"), SrcSpan { start: 0, end: 0 })],
257 "import nested/one",
258 );
259
260 let loaded = run_loader(fs, root, artefact);
261 assert_eq!(loaded.to_compile, vec![EcoString::from("two")]);
262}
263
264#[test]
265fn module_continues_to_be_stale_if_deps_get_updated() {
266 let fs = InMemoryFileSystem::new();
267 let root = Utf8Path::new("/");
268 let artefact = Utf8Path::new("/artefact");
269
270 // Cache is stale
271 write_src(&fs, "/src/one.gleam", 1, TEST_SOURCE_2);
272 write_cache(&fs, "one", 0, vec![], TEST_SOURCE_1);
273
274 // Cache is fresh but dep is stale
275 write_src(&fs, "/src/two.gleam", 1, "import one");
276 write_cache(
277 &fs,
278 "two",
279 2,
280 vec![(EcoString::from("one"), SrcSpan { start: 0, end: 0 })],
281 "import one",
282 );
283
284 // Cache is fresh
285 write_src(&fs, "/src/three.gleam", 1, TEST_SOURCE_1);
286 write_cache(&fs, "three", 2, vec![], TEST_SOURCE_1);
287
288 let _loaded1 = run_loader(fs.clone(), root, artefact);
289
290 // update the dependency
291 write_cache(&fs, "one", 3, vec![], TEST_SOURCE_2);
292 let loaded2 = run_loader(fs, root, artefact);
293
294 assert_eq!(loaded2.to_compile, vec![EcoString::from("two")]);
295 assert_eq!(
296 loaded2.cached,
297 vec![EcoString::from("one"), EcoString::from("three")]
298 );
299}
300
301#[test]
302fn invalid_module_name() {
303 let fs = InMemoryFileSystem::new();
304 let root = Utf8Path::new("/");
305 let artefact = Utf8Path::new("/artefact");
306
307 // Cache is stale
308 write_src(&fs, "/src/One.gleam", 1, TEST_SOURCE_2);
309
310 let loaded = run_loader(fs, root, artefact);
311 assert!(loaded.to_compile.is_empty());
312 assert!(loaded.cached.is_empty());
313 assert_eq!(
314 loaded.warnings,
315 vec![Warning::InvalidSource {
316 path: Utf8PathBuf::from("/src/One.gleam"),
317 }],
318 );
319}
320
321#[test]
322fn invalid_nested_module_name() {
323 let fs = InMemoryFileSystem::new();
324 let root = Utf8Path::new("/");
325 let artefact = Utf8Path::new("/artefact");
326
327 // Cache is stale
328 write_src(&fs, "/src/1/one.gleam", 1, TEST_SOURCE_2);
329
330 let loaded = run_loader(fs, root, artefact);
331 assert!(loaded.to_compile.is_empty());
332 assert!(loaded.cached.is_empty());
333 assert_eq!(
334 loaded.warnings,
335 vec![Warning::InvalidSource {
336 path: Utf8PathBuf::from("/src/1/one.gleam"),
337 }],
338 );
339}
340
341#[test]
342fn invalid_module_name_in_test() {
343 let fs = InMemoryFileSystem::new();
344 let root = Utf8Path::new("/");
345 let artefact = Utf8Path::new("/artefact");
346
347 // Cache is stale
348 write_src(&fs, "/test/One.gleam", 1, TEST_SOURCE_2);
349
350 let loaded = run_loader(fs, root, artefact);
351 assert!(loaded.to_compile.is_empty());
352 assert!(loaded.cached.is_empty());
353 assert_eq!(
354 loaded.warnings,
355 vec![Warning::InvalidSource {
356 path: Utf8PathBuf::from("/test/One.gleam"),
357 }],
358 );
359}
360
361#[test]
362fn invalid_nested_module_name_in_test() {
363 let fs = InMemoryFileSystem::new();
364 let root = Utf8Path::new("/");
365 let artefact = Utf8Path::new("/artefact");
366
367 // Cache is stale
368 write_src(&fs, "/test/1/one.gleam", 1, TEST_SOURCE_2);
369
370 let loaded = run_loader(fs, root, artefact);
371 assert!(loaded.to_compile.is_empty());
372 assert!(loaded.cached.is_empty());
373 assert_eq!(
374 loaded.warnings,
375 vec![Warning::InvalidSource {
376 path: Utf8PathBuf::from("/test/1/one.gleam"),
377 }],
378 );
379}
380
381#[test]
382fn invalid_module_name_in_dev() {
383 let fs = InMemoryFileSystem::new();
384 let root = Utf8Path::new("/");
385 let artefact = Utf8Path::new("/artefact");
386
387 // Cache is stale
388 write_src(&fs, "/dev/One.gleam", 1, TEST_SOURCE_2);
389
390 let loaded = run_loader(fs, root, artefact);
391 assert!(loaded.to_compile.is_empty());
392 assert!(loaded.cached.is_empty());
393 assert_eq!(
394 loaded.warnings,
395 vec![Warning::InvalidSource {
396 path: Utf8PathBuf::from("/dev/One.gleam"),
397 }],
398 );
399}
400
401#[test]
402fn invalid_nested_module_name_in_dev() {
403 let fs = InMemoryFileSystem::new();
404 let root = Utf8Path::new("/");
405 let artefact = Utf8Path::new("/artefact");
406
407 // Cache is stale
408 write_src(&fs, "/dev/1/one.gleam", 1, TEST_SOURCE_2);
409
410 let loaded = run_loader(fs, root, artefact);
411 assert!(loaded.to_compile.is_empty());
412 assert!(loaded.cached.is_empty());
413 assert_eq!(
414 loaded.warnings,
415 vec![Warning::InvalidSource {
416 path: Utf8PathBuf::from("/dev/1/one.gleam"),
417 }],
418 );
419}
420
421#[test]
422fn cache_files_are_removed_when_source_removed() {
423 let fs = InMemoryFileSystem::new();
424 let root = Utf8Path::new("/");
425 let artefact = Utf8Path::new("/artefact");
426
427 // Source is removed, cache is present
428 write_cache(&fs, "nested/one", 0, vec![], TEST_SOURCE_1);
429
430 _ = run_loader(fs.clone(), root, artefact);
431
432 assert_eq!(fs.files().len(), 0);
433}