1+ app [main!] {
2+ pf: platform " ../platform/main.roc" ,
3+ json: " https://github.com/lukewilliamboswell/roc-json/releases/download/0.13.0/RqendgZw5e1RsQa3kFhgtnMP8efWoqGRsAvubx4-zus.tar.br" ,
4+ }
5+
6+ import pf.Stdout
7+ import pf.Stderr
8+ import pf.Path
9+ import pf.Arg exposing [Arg ]
10+ import pf.Cmd
11+ import json.Json
12+
13+ main ! : List Arg => Result {} _
14+ main ! = |_args |
15+ Stdout . line !(
16+ " " "
17+ Testing Path functions...
18+ This will create and manipulate test files and directories in the current directory.
19+
20+ " " "
21+ )?
22+
23+ # Test path creation
24+ test_path_creation!({})?
25+
26+ # Test file operations
27+ test_file_operations!({})?
28+
29+ # Test directory operations
30+ test_directory_operations!({})?
31+
32+ # Test hard link creation
33+ test_hard_link!({})?
34+
35+ Stdout . line !("\n ✓ All Path function tests completed! ✓" )
36+
37+ test_path_creation! : {} => Result {} _
38+ test_path_creation! = |{}|
39+ Stdout.line!(" Testing Path . from_bytes and Path . with_extension :")?
40+
41+ # Test Path.from_bytes
42+ path_bytes = [116 , 101 , 115 , 116 , 95 , 112 , 97 , 116 , 104 ] # "test_path" in bytes
43+ path_from_bytes = Path . from_bytes (path_bytes )
44+ expected_str = " test_path"
45+ actual_str = Path . display (path_from_bytes )
46+
47+ # Test Path.with_extension
48+ base_path = Path . from_str ("test_file ")
49+ path_with_ext = Path . with_extension (base_path , " txt" )
50+
51+ path_with_dot = Path . from_str ("test_file .")
52+ path_dot_ext = Path . with_extension (path_with_dot , " json" )
53+
54+ path_replace_ext = Path . from_str ("test_file .old ")
55+ path_new_ext = Path . with_extension (path_replace_ext , " new" )
56+
57+ Stdout . line !(
58+ " " "
59+ Created path from bytes: ${Path.display(path_from_bytes)}
60+ Path.from_bytes result matches expected: ${Inspect.to_str(actual_str == expected_str)}
61+ Path with extension: ${Path.display(path_with_ext)}
62+ Extension added correctly: ${Inspect.to_str(Path.display(path_with_ext) == " test_file. txt " )}
63+ Path with dot and extension: ${Path.display(path_dot_ext)}
64+ Extension after dot: ${Inspect.to_str(Path.display(path_dot_ext) == " test_file. json " )}
65+ Path with replaced extension: ${Path.display(path_new_ext)}
66+ Extension replaced: ${Inspect.to_str(Path.display(path_new_ext) == " test_file. new " )}
67+ " " "
68+ )?
69+
70+ Ok ({})
71+
72+ test_file_operations ! : {} => Result {} _
73+ test_file_operations ! = |{}|
74+ Stdout . line !("\nTesting Path file operations:" )?
75+
76+ # Test Path.write_bytes! and Path.read_bytes!
77+ test_bytes = [72, 101, 108, 108, 111, 44, 32, 80, 97, 116, 104, 33] # " Hello , Path !" in bytes
78+ bytes_path = Path.from_str(" test_path_bytes. txt " )
79+ Path.write_bytes!(test_bytes, bytes_path)?
80+
81+ # Verify file exists using ls
82+ ls_output = Cmd.new(" ls" ) |> Cmd.args([" - la" , " test_path_bytes. txt " ]) |> Cmd.output!()
83+ ls_stdout = Str.from_utf8(ls_output.stdout) ? |_| LsInvalidUtf8
84+
85+ read_bytes = Path.read_bytes!(bytes_path)?
86+
87+ Stdout.line!(
88+ " " "
89+ File created: ${ls_stdout}
90+ Bytes written: ${Inspect . to_str (test_bytes )}
91+ Bytes read: ${Inspect . to_str (read_bytes )}
92+ Bytes match: ${Inspect . to_str (test_bytes == read_bytes)}
93+ " " "
94+ )?
95+
96+ # Test Path.write_utf8! and Path.read_utf8!
97+ utf8_content = " Hello from Path module! 🚀"
98+ utf8_path = Path.from_str(" test_path_utf8. txt " )
99+ Path.write_utf8!(utf8_content, utf8_path)?
100+
101+ # Check file content with cat
102+ cat_output = Cmd.new(" cat" ) |> Cmd.args([" test_path_utf8. txt " ]) |> Cmd.output!()
103+ cat_stdout = Str.from_utf8(cat_output.stdout) ? |_| CatInvalidUtf8
104+
105+ read_utf8 = Path.read_utf8!(utf8_path)?
106+
107+ Stdout.line!(
108+ " " "
109+ File content via cat: ${cat_stdout}
110+ UTF - 8 written: ${utf8_content}
111+ UTF - 8 read: ${read_utf8}
112+ UTF - 8 content matches: ${Inspect . to_str (utf8_content == read_utf8)}
113+ " " "
114+ )?
115+
116+ # Test Path.write! with JSON encoding
117+ json_data = { message: " Path test" , numbers: [1, 2, 3] }
118+ json_path = Path.from_str(" test_path_json. json " )
119+ Path.write!(json_data, json_path, Json.utf8)?
120+
121+ json_content = Path.read_utf8!(json_path)?
122+
123+ # Verify it's valid JSON by checking it contains expected fields
124+ contains_message = Str.contains(json_content, " \" message\" " )
125+ contains_numbers = Str . contains (json_content , " \" numbers\" " )
126+
127+ Stdout . line !(
128+ " " "
129+ JSON content: ${json_content}
130+ JSON contains 'message' field: ${Inspect.to_str(contains_message)}
131+ JSON contains 'numbers' field: ${Inspect.to_str(contains_numbers)}
132+ " " "
133+ )?
134+
135+ # Test Path.delete!
136+ delete_path = Path . from_str ("test_to_delete .txt ")
137+ Path . write_utf8 !("This file will be deleted" , delete_path)?
138+
139+ # Verify file exists before deletion
140+ ls_before = Cmd.new(" ls" ) |> Cmd.args([" test_to_delete. txt " ]) |> Cmd.output!()
141+
142+ Stdout.line!(" hey" )?
143+
144+ Path.delete!(delete_path) ? |err| DeleteFailed(err)
145+
146+ Stdout.line!(" yo" )?
147+
148+ # Verify file is gone after deletion
149+ ls_after = Cmd.new(" ls" ) |> Cmd.args([" test_to_delete. txt " ]) |> Cmd.output!()
150+
151+ Stdout.line!(" after" )?
152+
153+ Stdout.line!(
154+ " " "
155+ File exists before delete: ${Inspect . to_str (ls_before .status ? == 0 )}
156+ File exists after delete: ${Inspect . to_str (ls_after .status ? == 0 )}
157+ ✓ Successfully deleted test file
158+ " " "
159+ )?
160+
161+ Ok({})
162+
163+ test_directory_operations! : {} => Result {} _
164+ test_directory_operations! = |{}|
165+ Stdout.line!(" \nTesting Path directory operations:" )?
166+
167+ # Test Path.create_dir!
168+ single_dir = Path.from_str(" test_single_dir" )
169+ Path.create_dir!(single_dir)?
170+
171+ # Verify directory exists
172+ ls_dir = Cmd.new(" ls" ) |> Cmd.args([" - ld" , " test_single_dir" ]) |> Cmd.output!()
173+ ls_dir_stdout = Str.from_utf8(ls_dir.stdout) ? |_| LsDirInvalidUtf8
174+ is_dir = Str.starts_with(ls_dir_stdout, " d" )
175+
176+ Stdout.line!(
177+ " " "
178+ Created directory: ${ls_dir_stdout}
179+ Is directory (starts with ' d' ): ${Inspect . to_str (is_dir )}
180+ " " "
181+ )?
182+
183+ # Test Path.create_all! (nested directories)
184+ nested_dir = Path.from_str(" test_parent/ test_child/ test_grandchild" )
185+ Path.create_all!(nested_dir)?
186+
187+ # Verify nested structure with tree or find
188+ find_output = Cmd.new(" find" ) |> Cmd.args([" test_parent" , " - type" , " d" ]) |> Cmd.output!()
189+ find_stdout = Str.from_utf8(find_output.stdout) ? |_| FindInvalidUtf8
190+
191+ # Count directories created
192+ dir_count = Str.split_on(find_stdout, " \n" ) |> List.len
193+
194+ Stdout.line!(
195+ " " "
196+ Nested directory structure:
197+ ${find_stdout}
198+ Number of directories created: ${Num . to_str (dir_count - 1 )}
199+ " " "
200+ )?
201+
202+ # Create some files in the directory for testing
203+ Path.write_utf8!(" File 1 " , Path.from_str(" test_single_dir/ file1. txt " ))?
204+ Path.write_utf8!(" File 2 " , Path.from_str(" test_single_dir/ file2. txt " ))?
205+ Path.create_dir!(Path.from_str(" test_single_dir/ subdir" ))?
206+
207+ # List directory contents
208+ ls_contents = Cmd.new(" ls" ) |> Cmd.args([" - la" , " test_single_dir" ]) |> Cmd.output!()
209+ ls_contents_stdout = Str.from_utf8(ls_contents.stdout) ? |_| LsContentsInvalidUtf8
210+
211+ Stdout.line!(
212+ " " "
213+ Directory contents:
214+ ${ls_contents_stdout}
215+ " " "
216+ )?
217+
218+ # Test Path.delete_empty!
219+ empty_dir = Path.from_str(" test_empty_dir" )
220+ Path.create_dir!(empty_dir)?
221+
222+ # Verify it exists
223+ ls_empty_before = Cmd.new(" ls" ) |> Cmd.args([" - ld" , " test_empty_dir" ]) |> Cmd.output!()
224+
225+ Path.delete_empty!(empty_dir)?
226+
227+ # Verify it's gone
228+ ls_empty_after = Cmd.new(" ls" ) |> Cmd.args([" - ld" , " test_empty_dir" ]) |> Cmd.output!()
229+
230+ Stdout.line!(
231+ " " "
232+ Empty dir exists before delete: ${Inspect . to_str (ls_empty_before .status ? == 0 )}
233+ Empty dir exists after delete: ${Inspect . to_str (ls_empty_after .status ? == 0 )}
234+ " " "
235+ )?
236+
237+ # Test Path.delete_all!
238+ # First show what we're about to delete
239+ du_output = Cmd.new(" du" ) |> Cmd.args([" - sh" , " test_parent" ]) |> Cmd.output!()
240+ du_stdout = Str.from_utf8(du_output.stdout) ? |_| DuInvalidUtf8
241+
242+ Path.delete_all!(Path.from_str(" test_parent" ))?
243+
244+ # Verify it's gone
245+ ls_parent_after = Cmd.new(" ls" ) |> Cmd.args([" test_parent" ]) |> Cmd.output!()
246+
247+ Stdout.line!(
248+ " " "
249+ Size before delete_all: ${du_stdout}
250+ Parent dir exists after delete_all: ${Inspect . to_str (ls_parent_after .status ? == 0 )}
251+ ✓ Recursively deleted directory tree
252+ " " "
253+ )?
254+
255+ # Clean up remaining test directory
256+ Path.delete_all!(single_dir)?
257+
258+ Ok({})
259+
260+ test_hard_link! : {} => Result {} _
261+ test_hard_link! = |{}|
262+ Stdout.line!(" \nTesting Path . hard_link !:")?
263+
264+ # Create original file
265+ original_path = Path . from_str ("test_path_original .txt ")
266+ Path . write_utf8 !("Original content for Path hard link test" , original_path)?
267+
268+ # Get original file stats
269+ stat_before = Cmd.new(" stat" ) |> Cmd.args([" - c" , " %h" , " test_path_original. txt " ]) |> Cmd.output!()
270+ links_before = Str.from_utf8(stat_before.stdout) ? |_| StatBeforeInvalidUtf8
271+
272+ # Create hard link
273+ link_path = Path.from_str(" test_path_hardlink. txt " )
274+ when Path.hard_link!(original_path, link_path) is
275+ Ok({}) ->
276+ # Get link count after
277+ stat_after = Cmd.new(" stat" ) |> Cmd.args([" - c" , " %h" , " test_path_original. txt " ]) |> Cmd.output!()
278+ links_after = Str.from_utf8(stat_after.stdout) ? |_| StatAfterInvalidUtf8
279+
280+ # Verify both files exist and have same content
281+ original_content = Path.read_utf8!(original_path)?
282+ link_content = Path.read_utf8!(link_path)?
283+
284+ Stdout.line!(
285+ " " "
286+ ✓ Successfully created hard link
287+ Hard link count before: ${Str . trim (links_before )}
288+ Hard link count after: ${Str . trim (links_after )}
289+ Original content: ${original_content}
290+ Link content: ${link_content}
291+ Content matches: ${Inspect . to_str (original_content == link_content)}
292+ " " "
293+ )?
294+
295+ # Check inodes are the same
296+ ls_li_output =
297+ Cmd.new(" ls" )
298+ |> Cmd.args([" - li" , " test_path_original. txt " , " test_path_hardlink. txt " ])
299+ |> Cmd.output!()
300+
301+ ls_li_stdout_utf8 = Str.from_utf8(ls_li_output.stdout) ? |_| LsLiInvalidUtf8
302+
303+ inodes =
304+ Str.split_on(ls_li_stdout_utf8, " \n" )
305+ |> List.map(|line|
306+ Str.split_on(line, " " )
307+ |> List.take_first(1)
308+ )
309+
310+ first_inode = List.get(inodes, 0) ? |_| FirstInodeNotFound
311+ second_inode = List.get(inodes, 1) ? |_| SecondInodeNotFound
312+
313+ Stdout.line!(
314+ " " "
315+ Inode information:
316+ ${ls_li_stdout_utf8}
317+ First file inode: ${Inspect . to_str (first_inode )}
318+ Second file inode: ${Inspect . to_str (second_inode )}
319+ Inodes are equal: ${Inspect . to_str (first_inode == second_inode)}
320+ " " "
321+ )?
322+
323+ Err(err) ->
324+ Stderr.line!(" ✗ Hard link creation failed: ${Inspect . to_str (err )}")?
325+
326+ # Clean up test files
327+ cleanup_test_files!({})
328+
329+ cleanup_test_files ! : {} => Result {} _
330+ cleanup_test_files ! = |{}|
331+ Stdout . line !("\nCleaning up test files..." )?
332+
333+ test_files = [
334+ " test_path_bytes. txt " ,
335+ " test_path_utf8. txt " ,
336+ " test_path_json. json " ,
337+ " test_path_original. txt " ,
338+ " test_path_hardlink. txt "
339+ ]
340+
341+ # Show files before cleanup
342+ ls_before_cleanup = Cmd.new(" ls" ) |> Cmd.args([" - la" ] |> List.concat(test_files)) |> Cmd.output!()
343+
344+ if ls_before_cleanup.status? == 0 then
345+ cleanup_stdout = Str.from_utf8(ls_before_cleanup.stdout) ? |_| CleanupInvalidUtf8
346+
347+ Stdout.line!(
348+ " " "
349+ Files to clean up:
350+ ${cleanup_stdout}
351+ " " "
352+ )?
353+
354+ List.for_each_try!(test_files, |filename|
355+ Path.delete!(Path.from_str(filename))
356+ )?
357+
358+ # Verify cleanup
359+ ls_after_cleanup = Cmd.new(" ls" ) |> Cmd.args(test_files) |> Cmd.output!()
360+
361+ Stdout.line!(
362+ " " "
363+ Files remaining after cleanup: ${Inspect . to_str (ls_after_cleanup .status ? == 0 )}
364+ ✓ Deleted all test files.
365+ " " "
366+ )
367+ else
368+ Stderr.line!(" ✗ Error listing files before cleanup: `ls - la ...` exited with non- zero exit code:\n\t${Inspect . to_str (ls_before_cleanup )}")
0 commit comments