Skip to content

Test Implementation for Path.GetRelativePath #773

Description

@zach-delong

Is your feature request related to a problem? Please describe.
I am trying to implement a static site generator, and one of the tasks I need to do is adjust file paths handed to me to be relative to the root of the "static site" I am trying to create. This is something that Microsoft makes available via Path.GetRelativePath, and the normal implementation of it in System.IO.Abstractions seems to work great, but the Mock implementation is not being overwritten so the result is strange.

Describe the solution you'd like
I think it should be possible to write some kind of mock implementation that would use the mock file system as reference.

Describe alternatives you've considered
I could probably also implement this as a specific mock for my project, but wanted to see if this was something the community had an interest in.

Additional context
If desired, my plan is to start prototyping this out today, but I can't make any promises on how fast it'll go.

This is the example code that I need to "mock" the behavior of and this is a prototype test that currently behaves very strangely because of the GetRelativePath mock implementation.

Activity

  1. zach-delong commented on Nov 29, 2021

    @zach-delong
    Author

    After spending some more time debugging and looking into this issue, I realize now that what I am dealing with is probably a strange edge case, and not as pervasive as I previously thought, which is great! I'm still a bit confused by it, though. This example test always fails:

    [Fact]
    public void Foo()
    {
            var fs = new MockFileSystem();
    
            fs.AddDirectory("input");
            fs.AddDirectory("output");
    
            fs.AddFile("input/a.txt", "foo");
    
            var result = fs.Path.GetRelativePath("/input", "a.txt");
    
            Assert.Equal("a.txt", result);
    }
    

    But if you change "/input" to "input" in the GetRelativePath call, it all works as expected. In reality, in this example, /input is what is created either way, but result will have the path to my test DLL when run.

  2. fgreinacher commented on Dec 1, 2021

    @fgreinacher
    Contributor

    Thanks for reporting this and sharing some test cases!

    Which OS are you running the test on? /input is something different than input so I'd not really expected these tests to succeed. Can you share the test results here so that its easier to understand what's going on?

  3. zach-delong commented on Dec 14, 2021

    @zach-delong
    Author

    I am running this on Linux. So after digging around what I have realized is that if your path doesn't exist relative to the path provided, the API does strange things reaching out to the file system to figure out how to complete it. Or at least that's what it seems like. The two tests for this behavior are this:

    public void Foo()
    {
            var fs = new MockFileSystem();
    
            fs.AddDirectory("input");
            fs.AddDirectory("output");
    
            fs.AddFile("input/a.txt", "foo");
    
            var result = fs.Path.GetRelativePath("/input", "a.txt");
    
            Assert.Equal("a.txt", result);
    }
    

    and

    public void Foo()
    {
            var fs = new MockFileSystem();
    
            fs.AddDirectory("input");
            fs.AddDirectory("output");
    
            fs.AddFile("input/a.txt", "foo");
    
            var result = fs.Path.GetRelativePath("input", "a.txt");
    
            Assert.Equal("a.txt", result);
    }
    

    If you examine you will find that in both cases, the path /input/a.txt exists, but in the first case, the relative path result will be a path to the executing DLL (in my case a test project DLL). It seems like the system path method that is being proxied through is doing more than just string manipulation. The documentation points out that it calls "Paths are resolved by calling the GetFullPath method..." which would suggest that it's reaching out to the underlying file system and not just doing string manipulation like the comments in the test project seem to suggest.

    I need to find some more time to test, to confirm or deny that theory.

  4. fgreinacher commented on Dec 17, 2021

    @fgreinacher
    Contributor

    Thanks for the detailed analysis! The .NET implementation seems to do quite a lot of stuff, it's totally possible that we need to override (certain parts of) it in our mock implementation.

  5. added
    state: ready to pickIssues that are ready for being worked on
    and removed on Dec 17, 2021
  6. MattKotsenas commented on Jul 23, 2023

    @MattKotsenas

    I just bumped into this as well. Debugging into the base implementation of Path.GetRelativePath(), (and as @ZacheryPD mentioned) it calls GetFullPath(), which the mock can't intercept (note I'm currently on Windows, so it's possible the behavior on Linux is different).

    In my case I can get the MockFileSystem to behave the same if I switch from the new MockFileSystem() constructor to one like this:

    new MockFileSystem(files: null, Environment.CurrentDirectory)

    (see https://github.com/TestableIO/System.IO.Abstractions/blob/d0745332db8d46825e10bf289883309c375cc550/src/TestableIO.System.IO.Abstractions.TestingHelpers/MockFileSystem.cs#L27C67-L27C100)

    Then, the MockFileSystem uses the same current directory when creating mock files as will be resolved by the Path class. I think it might make sense to update the default constructor of MockFileSystem to do something similar.

    If you have any questions / concerns, please let me know! I can also provide a repo case if necessary.

  7. vbreuss commented on Aug 1, 2023

    @vbreuss
    Member

    Fixed with #1012

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: testinghelpersIssues that address the testing helpersstate: ready to pickIssues that are ready for being worked ontype: enhancementIssues that propose new functionality

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions