@@ -149,9 +149,53 @@ mod formatter {
149149
150150 /// Compute the relative path from the root directory.
151151 fn relative_to ( root : & Path , path : & Path ) -> Result < PathBuf , Error > {
152- // The implementation is naive; it assumes that the path is a child of the root.
153- let relative_path = path. strip_prefix ( root) ?;
154- Ok ( relative_path. to_path_buf ( ) )
152+ // This is a naive implementation that assumes the root is
153+ // on the same filesystem/volume as the path.
154+ let mut root_components = root. components ( ) ;
155+ let mut path_components = path. components ( ) ;
156+
157+ let mut remaining_root_components = Vec :: new ( ) ;
158+ let mut remaining_path_components = Vec :: new ( ) ;
159+
160+ // Find the common prefix
161+ loop {
162+ let root_comp = root_components. next ( ) ;
163+ let path_comp = path_components. next ( ) ;
164+ match ( root_comp, path_comp) {
165+ ( Some ( root) , Some ( path) ) if root != path => {
166+ remaining_root_components. push ( root) ;
167+ remaining_root_components. extend ( root_components) ;
168+ remaining_path_components. push ( path) ;
169+ remaining_path_components. extend ( path_components) ;
170+ break ;
171+ }
172+ ( Some ( root) , None ) => {
173+ remaining_root_components. push ( root) ;
174+ remaining_root_components. extend ( root_components) ;
175+ break ;
176+ }
177+ ( None , Some ( path) ) => {
178+ remaining_path_components. push ( path) ;
179+ remaining_path_components. extend ( path_components) ;
180+ break ;
181+ }
182+ ( None , None ) => break ,
183+ _ => continue ,
184+ }
185+ }
186+
187+ // Count remaining components in the root to determine how many `..` are needed
188+ let mut result = PathBuf :: new ( ) ;
189+ for _ in remaining_root_components {
190+ result. push ( ".." ) ;
191+ }
192+
193+ // Add the remaining components of the path
194+ for comp in remaining_path_components {
195+ result. push ( comp) ;
196+ }
197+
198+ Ok ( result)
155199 }
156200
157201 /// Convenient function to resolve the path based on the configuration.
@@ -212,6 +256,208 @@ mod formatter {
212256 }
213257 }
214258 }
259+
260+ #[ cfg( test) ]
261+ mod formatter_tests {
262+ use super :: * ;
263+ use crate :: config:: { PathFormat , PathResolver } ;
264+ use crate :: semantic:: { CompilerCall , CompilerPass } ;
265+ use crate :: vec_of_strings;
266+ use std:: fs;
267+ use std:: path:: PathBuf ;
268+ use tempfile:: tempdir;
269+
270+ #[ test]
271+ fn test_absolute_to ( ) {
272+ // The test creates a temporary directory and a file in it.
273+ // Then it verifies that the absolute path of the file is correct.
274+ //
275+ // E.g., `/tmp/tmpdir/file.txt` is the absolute path of the file,
276+ // if `/tmp/tmpdir` is the root directory and `file.txt` is the file.
277+ let root_dir = tempdir ( ) . unwrap ( ) ;
278+ let root_dir_path = root_dir. path ( ) ;
279+
280+ let file_path = root_dir_path. join ( "file.txt" ) ;
281+ fs:: write ( & file_path, "content" ) . unwrap ( ) ;
282+
283+ let file_relative_path = PathBuf :: from ( "file.txt" ) ;
284+
285+ let result = absolute_to ( root_dir_path, & file_relative_path) . unwrap ( ) ;
286+ assert_eq ! ( result, file_path) ;
287+
288+ let result = absolute_to ( root_dir_path, & file_path) . unwrap ( ) ;
289+ assert_eq ! ( result, file_path) ;
290+
291+ let result = absolute_to ( root_dir_path, root_dir_path) . unwrap ( ) ;
292+ assert_eq ! ( result, root_dir_path) ;
293+ }
294+
295+ #[ test]
296+ fn test_relative_to ( ) {
297+ // The test creates two temporary directories and a file in the first one.
298+ // Then it verifies that the relative path from the second directory to the file
299+ // in the first directory is correct.
300+ //
301+ // E.g., `../tmpdir/file.txt` is the relative path to the file,
302+ // if `/tmp/tmpdir2` is the root directory and `/tmp/tmpdir/file.txt` is the file.
303+ let a_dir = tempdir ( ) . unwrap ( ) ;
304+ let a_dir_path = a_dir. path ( ) ;
305+ let a_dir_name = a_dir_path. file_name ( ) . unwrap ( ) ;
306+
307+ let file_path = a_dir_path. join ( "file.txt" ) ;
308+ fs:: write ( & file_path, "content" ) . unwrap ( ) ;
309+
310+ let b_dir = tempdir ( ) . unwrap ( ) ;
311+ let b_dir_path = b_dir. path ( ) ;
312+
313+ let result = relative_to ( b_dir_path, & file_path) . unwrap ( ) ;
314+ assert_eq ! (
315+ result,
316+ PathBuf :: from( ".." ) . join( a_dir_name) . join( "file.txt" )
317+ ) ;
318+
319+ let result = relative_to ( a_dir_path, & file_path) . unwrap ( ) ;
320+ assert_eq ! ( result, PathBuf :: from( "file.txt" ) ) ;
321+ }
322+
323+ #[ test]
324+ fn test_path_resolver ( ) {
325+ let root_dir = tempdir ( ) . unwrap ( ) ;
326+ let root_dir_path = root_dir. path ( ) ;
327+
328+ let file_path = root_dir_path. join ( "file.txt" ) ;
329+ fs:: write ( & file_path, "content" ) . unwrap ( ) ;
330+
331+ let resolver = PathResolver :: Canonical ;
332+ let result = resolver. resolve ( root_dir_path, & file_path) . unwrap ( ) ;
333+ assert_eq ! ( result, file_path) ;
334+
335+ let resolver = PathResolver :: Relative ;
336+ let result = resolver. resolve ( root_dir_path, & file_path) . unwrap ( ) ;
337+ assert_eq ! ( result, PathBuf :: from( "file.txt" ) ) ;
338+ }
339+
340+ #[ test]
341+ fn test_path_formatter_skip_format ( ) {
342+ let formatter = PathFormatter :: SkipFormat ;
343+
344+ let input = CompilerCall {
345+ compiler : PathBuf :: from ( "gcc" ) ,
346+ working_dir : PathBuf :: from ( "/project" ) ,
347+ passes : vec ! [ CompilerPass :: Compile {
348+ source: PathBuf :: from( "main.c" ) ,
349+ output: PathBuf :: from( "main.o" ) . into( ) ,
350+ flags: vec![ "-O2" . into( ) ] ,
351+ } ] ,
352+ } ;
353+
354+ let result = formatter. apply ( input. clone ( ) ) ;
355+ assert ! ( result. is_ok( ) ) ;
356+ assert_eq ! ( result. unwrap( ) , input) ;
357+ }
358+
359+ #[ test]
360+ fn test_path_formatter_do_format ( ) {
361+ let source_dir = tempdir ( ) . unwrap ( ) ;
362+ let source_dir_path = source_dir. path ( ) ;
363+ let source_dir_name = source_dir_path. file_name ( ) . unwrap ( ) ;
364+ let source_file_path = source_dir_path. join ( "main.c" ) ;
365+ fs:: write ( & source_file_path, "int main() {}" ) . unwrap ( ) ;
366+
367+ let build_dir = tempdir ( ) . unwrap ( ) ;
368+ let build_dir_path = build_dir. path ( ) ;
369+ let build_dir_name = build_dir_path. file_name ( ) . unwrap ( ) ;
370+ let output_file_path = build_dir_path. join ( "main.o" ) ;
371+ fs:: write ( & output_file_path, "object" ) . unwrap ( ) ;
372+
373+ let execution_dir = tempdir ( ) . unwrap ( ) ;
374+ let execution_dir_path = execution_dir. path ( ) ;
375+
376+ // The entry contains compiler call with absolute paths.
377+ let input = CompilerCall {
378+ compiler : PathBuf :: from ( "gcc" ) ,
379+ working_dir : build_dir_path. to_path_buf ( ) ,
380+ passes : vec ! [ CompilerPass :: Compile {
381+ source: source_file_path. clone( ) ,
382+ output: output_file_path. clone( ) . into( ) ,
383+ flags: vec_of_strings![ "-O2" ] ,
384+ } ] ,
385+ } ;
386+
387+ {
388+ let sut = PathFormatter :: DoFormat (
389+ PathFormat {
390+ directory : PathResolver :: Canonical ,
391+ file : PathResolver :: Canonical ,
392+ output : PathResolver :: Canonical ,
393+ } ,
394+ execution_dir_path. to_path_buf ( ) ,
395+ ) ;
396+
397+ let expected = CompilerCall {
398+ compiler : input. compiler . clone ( ) ,
399+ working_dir : build_dir_path. to_path_buf ( ) ,
400+ passes : vec ! [ CompilerPass :: Compile {
401+ source: source_file_path. to_path_buf( ) ,
402+ output: output_file_path. to_path_buf( ) . into( ) ,
403+ flags: vec_of_strings![ "-O2" ] ,
404+ } ] ,
405+ } ;
406+
407+ let result = sut. apply ( input. clone ( ) ) ;
408+ assert ! ( result. is_ok( ) ) ;
409+ assert_eq ! ( result. unwrap( ) , expected) ;
410+ }
411+ {
412+ let sut = PathFormatter :: DoFormat (
413+ PathFormat {
414+ directory : PathResolver :: Canonical ,
415+ file : PathResolver :: Relative ,
416+ output : PathResolver :: Relative ,
417+ } ,
418+ execution_dir_path. to_path_buf ( ) ,
419+ ) ;
420+
421+ let expected = CompilerCall {
422+ compiler : input. compiler . clone ( ) ,
423+ working_dir : build_dir_path. to_path_buf ( ) ,
424+ passes : vec ! [ CompilerPass :: Compile {
425+ source: PathBuf :: from( ".." ) . join( source_dir_name) . join( "main.c" ) ,
426+ output: PathBuf :: from( "main.o" ) . into( ) ,
427+ flags: vec_of_strings![ "-O2" ] ,
428+ } ] ,
429+ } ;
430+
431+ let result = sut. apply ( input. clone ( ) ) ;
432+ assert ! ( result. is_ok( ) ) ;
433+ assert_eq ! ( result. unwrap( ) , expected) ;
434+ }
435+ {
436+ let sut = PathFormatter :: DoFormat (
437+ PathFormat {
438+ directory : PathResolver :: Relative ,
439+ file : PathResolver :: Relative ,
440+ output : PathResolver :: Relative ,
441+ } ,
442+ execution_dir_path. to_path_buf ( ) ,
443+ ) ;
444+
445+ let expected = CompilerCall {
446+ compiler : input. compiler . clone ( ) ,
447+ working_dir : PathBuf :: from ( ".." ) . join ( build_dir_name) ,
448+ passes : vec ! [ CompilerPass :: Compile {
449+ source: PathBuf :: from( ".." ) . join( source_dir_name) . join( "main.c" ) ,
450+ output: PathBuf :: from( "main.o" ) . into( ) ,
451+ flags: vec_of_strings![ "-O2" ] ,
452+ } ] ,
453+ } ;
454+
455+ let result = sut. apply ( input. clone ( ) ) ;
456+ assert ! ( result. is_ok( ) ) ;
457+ assert_eq ! ( result. unwrap( ) , expected) ;
458+ }
459+ }
460+ }
215461}
216462
217463mod filter {
@@ -428,15 +674,7 @@ mod filter {
428674 } ] ,
429675 working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
430676 } ;
431- let expected = CompilerCall {
432- compiler : std:: path:: PathBuf :: from ( "gcc" ) ,
433- passes : vec ! [ CompilerPass :: Compile {
434- source: PathBuf :: from( "main.c" ) ,
435- output: PathBuf :: from( "main.o" ) . into( ) ,
436- flags: vec![ "-O2" . into( ) ] ,
437- } ] ,
438- working_dir : std:: path:: PathBuf :: from ( "/project" ) ,
439- } ;
677+ let expected = input. clone ( ) ;
440678
441679 let compilers: Vec < Compiler > = vec ! [ ] ;
442680 let sut = SemanticFilter :: try_from ( compilers. as_slice ( ) ) ;
0 commit comments