Summary
IsDirectoryPathAttribute decides whether a path is valid by asking File.Exists(value). For a relative path that resolves against the process's current working directory, so whether a relative directory path is valid depends on what files happen to be sitting next to whatever is running.
That is not a hypothetical. It currently breaks ktsu.AppDataStorage on Linux, and through it every consumer of AppData.
How it shows up
AppData.AppDomain converts the app domain's name into a RelativeDirectoryPath. On Linux, .NET publishes an extensionless apphost next to the test assembly named exactly after the project, so a test project called ktsu.BlastMerge.Test has a file called ktsu.BlastMerge.Test in the directory the tests run from. File.Exists("ktsu.BlastMerge.Test") is therefore true, the attribute rejects the name, and the conversion throws:
System.FormatException: Cannot convert "ktsu.BlastMerge.Test" to RelativeDirectoryPath
at ktsu.StrongStrings.AnyStrongString.PerformValidation[TDest](TDest value)
at ktsu.Extensions.StringExtensions.As[TDest](String weakString)
at ktsu.AppDataStorage.AppData.get_AppDomain()
at ktsu.AppDataStorage.AppData.get_Path()
at ktsu.AppDataStorage.AppData`1.LoadOrCreate()
On Windows the apphost is ktsu.BlastMerge.Test.exe, so the same call finds nothing and the conversion succeeds. Nothing about the name differs between the two platforms; only what is on disk beside the process does.
Why this is worth separating from #191
#191 is about separators, character sets and length limits: what a path string means. This is about validation reaching out to the filesystem at all, and it fails differently:
- it depends on the current working directory, so the same string validates differently in two processes on the same machine
- it depends on unrelated files, so an unrelated build artifact changes whether a name is accepted
- it reaches consumers rather than tests:
ktsu.BlastMerge fails on Linux for this reason alone, with no defect of its own
Suggested shape
A relative path's validity should be decidable from the string. IsDirectoryPathAttribute's current check is trying to express "this is not an existing file", which is a meaningful question for an absolute path and not answerable for a relative one without inventing a base directory.
Options worth weighing:
- restrict the existence check to absolute paths and validate relative ones by shape alone
- drop the existence check from the type's validation and expose it as a separate query a caller makes deliberately, against a base directory it names
- keep it, but resolve against a base the caller supplies rather than the ambient working directory
Reproduction
git clone https://github.com/ktsu-dev/BlastMerge
cd BlastMerge
dotnet test --filter "FullyQualifiedName~AppDataBatchManagerTests" # on Linux
Every test in that class fails on the conversion above. The same command passes on Windows.
Summary
IsDirectoryPathAttributedecides whether a path is valid by askingFile.Exists(value). For a relative path that resolves against the process's current working directory, so whether a relative directory path is valid depends on what files happen to be sitting next to whatever is running.That is not a hypothetical. It currently breaks
ktsu.AppDataStorageon Linux, and through it every consumer ofAppData.How it shows up
AppData.AppDomainconverts the app domain's name into aRelativeDirectoryPath. On Linux, .NET publishes an extensionless apphost next to the test assembly named exactly after the project, so a test project calledktsu.BlastMerge.Testhas a file calledktsu.BlastMerge.Testin the directory the tests run from.File.Exists("ktsu.BlastMerge.Test")is therefore true, the attribute rejects the name, and the conversion throws:On Windows the apphost is
ktsu.BlastMerge.Test.exe, so the same call finds nothing and the conversion succeeds. Nothing about the name differs between the two platforms; only what is on disk beside the process does.Why this is worth separating from #191
#191 is about separators, character sets and length limits: what a path string means. This is about validation reaching out to the filesystem at all, and it fails differently:
ktsu.BlastMergefails on Linux for this reason alone, with no defect of its ownSuggested shape
A relative path's validity should be decidable from the string.
IsDirectoryPathAttribute's current check is trying to express "this is not an existing file", which is a meaningful question for an absolute path and not answerable for a relative one without inventing a base directory.Options worth weighing:
Reproduction
Every test in that class fails on the conversion above. The same command passes on Windows.