11// Copyright (c) The datatest-stable Contributors
22// SPDX-License-Identifier: MIT OR Apache-2.0
33
4- use camino:: { Utf8Path , Utf8PathBuf } ;
4+ use camino:: { Utf8Component , Utf8Path , Utf8PathBuf } ;
55
66#[ derive( Debug ) ]
77#[ doc( hidden) ]
88pub enum DataSource {
9+ // This is relative to the crate root, and stored with forward slashes.
910 Directory ( Utf8PathBuf ) ,
1011 #[ cfg( feature = "include-dir" ) ]
1112 IncludeDir ( std:: borrow:: Cow < ' static , include_dir:: Dir < ' static > > ) ,
@@ -31,18 +32,23 @@ impl DataSource {
3132 ///
3233 /// Used for `--exact` matches.
3334 pub ( crate ) fn derive_exact ( & self , filter : & str , test_name : & str ) -> Option < TestEntry > {
34- let rel_path = filter. strip_prefix ( test_name) ?. strip_prefix ( "::" ) ?;
35+ // include_dir 0.7.4 returns paths with forward slashes, including on
36+ // Windows. But that isn't part of the stable API it seems, so we call
37+ // `rel_path_to_forward_slashes` anyway.
38+ let rel_path = rel_path_to_forward_slashes (
39+ filter. strip_prefix ( test_name) ?. strip_prefix ( "::" ) ?. as_ref ( ) ,
40+ ) ;
3541 match self {
3642 DataSource :: Directory ( path) => Some ( TestEntry {
37- source : TestSource :: Path ( path. join ( rel_path) ) ,
38- rel_path : rel_path . into ( ) ,
43+ source : TestSource :: Path ( rel_path_to_forward_slashes ( & path. join ( & rel_path) ) ) ,
44+ rel_path,
3945 } ) ,
4046 #[ cfg( feature = "include-dir" ) ]
4147 DataSource :: IncludeDir ( dir) => {
42- let file = dir. get_file ( rel_path) ?;
48+ let file = dir. get_file ( & rel_path) ?;
4349 Some ( TestEntry {
4450 source : TestSource :: IncludeDir ( file) ,
45- rel_path : rel_path . into ( ) ,
51+ rel_path,
4652 } )
4753 }
4854 }
@@ -122,8 +128,15 @@ fn iter_include_dir<'a>(
122128 stack : dir. entries ( ) . iter ( ) . collect ( ) ,
123129 }
124130 . map ( |file| {
125- let rel_path = Utf8PathBuf :: try_from ( file. path ( ) . to_path_buf ( ) )
126- . map_err ( |error| error. into_io_error ( ) ) ?;
131+ // include_dir 0.7.4 returns paths with forward slashes, including on
132+ // Windows. But that isn't part of the stable API it seems, so we call
133+ // `rel_path_to_forward_slashes` anyway.
134+ let rel_path = match file. path ( ) . try_into ( ) {
135+ Ok ( path) => rel_path_to_forward_slashes ( path) ,
136+ Err ( error) => {
137+ return Err ( error. into_io_error ( ) ) ;
138+ }
139+ } ;
127140 Ok ( TestEntry {
128141 source : TestSource :: IncludeDir ( file) ,
129142 rel_path,
@@ -139,10 +152,11 @@ pub(crate) struct TestEntry {
139152
140153impl TestEntry {
141154 pub ( crate ) fn from_full_path ( root : & Utf8Path , path : Utf8PathBuf ) -> Self {
142- let rel_path = path
143- . strip_prefix ( root)
144- . unwrap_or_else ( |_| panic ! ( "failed to strip root '{}' from path '{}'" , root, path) )
145- . to_owned ( ) ;
155+ let path = rel_path_to_forward_slashes ( & path) ;
156+ let rel_path =
157+ rel_path_to_forward_slashes ( path. strip_prefix ( root) . unwrap_or_else ( |_| {
158+ panic ! ( "failed to strip root '{}' from path '{}'" , root, path)
159+ } ) ) ;
146160 Self {
147161 source : TestSource :: Path ( path) ,
148162 rel_path,
@@ -180,10 +194,18 @@ impl TestEntry {
180194 }
181195 }
182196
183- /// Returns the path to the test data.
197+ /// Returns the path to match regexes against.
198+ ///
199+ /// This is always the relative path to the file from the include directory.
200+ pub ( crate ) fn match_path ( & self ) -> & Utf8Path {
201+ & self . rel_path
202+ }
203+
204+ /// Returns the path to the test data, as passed into the test function.
184205 ///
185- /// For directories on disk, this is the absolute path. For `include_dir`
186- /// sources, this is the path relative to the root of the include directory.
206+ /// For directories on disk, this is the relative path after being joined
207+ /// with the include directory. For `include_dir` sources, this is the path
208+ /// relative to the root of the include directory.
187209 pub ( crate ) fn test_path ( & self ) -> & Utf8Path {
188210 match & self . source {
189211 TestSource :: Path ( path) => path,
@@ -219,9 +241,37 @@ impl TestEntry {
219241 }
220242}
221243
244+ #[ cfg( windows) ]
245+ #[ track_caller]
246+ fn rel_path_to_forward_slashes ( path : & Utf8Path ) -> Utf8PathBuf {
247+ assert ! ( is_truly_relative( path) , "path {path} must be relative" ) ;
248+ path. as_str ( ) . replace ( '\\' , "/" ) . into ( )
249+ }
250+
251+ #[ cfg( not( windows) ) ]
252+ #[ track_caller]
253+ fn rel_path_to_forward_slashes ( path : & Utf8Path ) -> Utf8PathBuf {
254+ assert ! ( is_truly_relative( path) , "path {path} must be relative" ) ;
255+ path. to_owned ( )
256+ }
257+
258+ /// Returns true if this is a path with no root-dir or prefix components.
259+ ///
260+ /// On Windows, unlike `path.is_relative()`, this rejects paths like "C:temp"
261+ /// and "\temp".
262+ #[ track_caller]
263+ fn is_truly_relative ( path : & Utf8Path ) -> bool {
264+ path. components ( ) . all ( |c| match c {
265+ Utf8Component :: Normal ( _) | Utf8Component :: CurDir | Utf8Component :: ParentDir => true ,
266+ Utf8Component :: RootDir | Utf8Component :: Prefix ( _) => false ,
267+ } )
268+ }
269+
222270#[ derive( Debug ) ]
223271#[ doc( hidden) ]
224272pub ( crate ) enum TestSource {
273+ /// A data source on disk, with the path being the relative path to the file
274+ /// from the crate root.
225275 Path ( Utf8PathBuf ) ,
226276 #[ cfg( feature = "include-dir" ) ]
227277 IncludeDir ( & ' static include_dir:: File < ' static > ) ,
@@ -256,7 +306,14 @@ pub mod data_source_kinds {
256306
257307 impl < T : ToString > AsDirectory for T {
258308 fn resolve_data_source ( self ) -> DataSource {
259- DataSource :: Directory ( self . to_string ( ) . into ( ) )
309+ let s = self . to_string ( ) ;
310+ let path = Utf8Path :: new ( & s) ;
311+
312+ if !is_truly_relative ( path) {
313+ panic ! ( "data source path must be relative: '{}'" , s) ;
314+ }
315+
316+ DataSource :: Directory ( rel_path_to_forward_slashes ( path) )
260317 }
261318 }
262319
@@ -290,6 +347,12 @@ pub mod data_source_kinds {
290347mod tests {
291348 use super :: * ;
292349
350+ #[ test]
351+ #[ should_panic = "data source path must be relative: '/absolute/path'" ]
352+ fn data_source_absolute_path_panics ( ) {
353+ data_source_kinds:: AsDirectory :: resolve_data_source ( "/absolute/path" ) ;
354+ }
355+
293356 #[ test]
294357 fn missing_test_name ( ) {
295358 assert_eq ! ( derive_test_path( "root" . into( ) , "file" , "test_name" ) , None ) ;
0 commit comments