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