Skip to content

IsDirectoryPathAttribute validates a relative path against the working directory #196

Description

@matt-edmondson

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions