Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to simulate File System on other platform #460

Open
markusschaber opened this issue Feb 21, 2024 · 17 comments · Fixed by #576
Open

How to simulate File System on other platform #460

markusschaber opened this issue Feb 21, 2024 · 17 comments · Fixed by #576
Labels
enhancement New feature or request state: released

Comments

@markusschaber
Copy link

Hi,
As far as I can see, your mocked file system implementation supports the semantics of Windows, Linux and MacOS.

However, I could not find how to tell which semantics I want when creating an instance of the mock.

Our use case would be to test our code against a file system with Windows and MacOS semantics in the CI pipeline which currently only runs on Linux.

@vbreuss
Copy link
Member

vbreuss commented Feb 21, 2024

Hello @markusschaber,

That's a very interesting use case, I never thought about. Currently we mostly distinguish between file systems via the Execute methods, which would (at least in theory) be able to be changed, so that the behaviour could be overwritten. However there are also some other edge cases to consider (probably there are even more, but these just came to my mind immediately):

  • All methods that rely on Path methods would need to be changed manually to use the correct directory separator depending on such a setting
  • Some methods only work on the corresponding OS and rely on .NET features that are not supported on another OS, e.g. File.GetUnixFileMode has an UnsupportedOSPlatform("windows") attribute and would throw when running on Windows. This attribute could not be changed at runtime with a setting.

The primary goal of this library is to behave (as much as possible) the same as the real file system on the corresponding platform, so removing the attributes or relaxing some requirements, would not really be an option.

Do you have any ideas, how to overcome these challenges?
I would be happy to elaborate further on it!

@markusschaber
Copy link
Author

The platform identification would neet to be instance members of the MockFileSystem, instead of statically accessing the Execute class. The default values would still reflect the current platform, so everything is strictly opt-in.

I had been falsely assuming that the mock implementation actually does not depend on any underlying file system code. You're right, some methods like Path would need reimplementation, so they can switch between behaviours at runtime. I guess the actual platform specific implementations could be gathered together from .NET itself (with appropriate attribution), as both projects use MIT license.

As far as I can see, the SupportedOSPlatformAttribute and UnsupportedOSPlatformAttribute are just for compile time analysis - I think their diagnostics could be suppressed for the test projects. The exceptions at runtime are actually thrown by the implementation, which could emulate the platform specific behaviour instead.

@vbreuss
Copy link
Member

vbreuss commented Feb 22, 2024

Let's talk about a possible approach:

  1. Make the Execute class not static and adding it to the MockFileSystem with a constructor that can receive an OSPlatform enum and react accordingly.

  2. Then we could add a method to replace it on the MockFileSystem, e.g.

    public MockFileSystem SimulatingOperatingSystem(OSPlatform osPlatform)
    {
      Execute = new Execute(osPlatform);
      return this;
    }

    and use this property instead of the static calls to Execute.

  3. Update all Path-related calls to use a custom implementation instead.

  4. Update tests, so that they also cover other OS platforms. Here I am still unsure, how to make sure, that the result matches the real file system.
    Any ideas?

  5. At this point, we would probably run in some additional necessary changes 😄

Would you be willing to help with such a large change?

@markusschaber
Copy link
Author

We should have the Tests run in 4 modes: Windows, MacOS, Linux and "native" - which is the OS we're actually running on.

I'm not exactly sure what's the best way to achieve this, as xUnit does not support parametrizing on the assembly/project level.

The approach I use is to write the tests in an abstract class, and then create a derived class per platform, where I just inject the platform in the base class via constructor.

I saw that there's quite some generated code, so maybe the multiplication could be wired into the code generator.

I'll check with my employer whether I get some permission to contribute some work.

@markusschaber
Copy link
Author

markusschaber commented Feb 25, 2024

I don't know the exact relationship between TestableIO and Testably projects. Which project would be the better place for this kind of contribution?

I've been under the impression that the Testably implementation might be the more advanced and "closer" to native FS behaviour, but we now have some unit tests which pass with native Windows and Linux FS, as well as the MockFileSystem from TestableIO 20.0.15, while they fail with Testably.Abstractions.Testing 2.6.1. (I did not have time to investigate any details yet.)

@vbreuss
Copy link
Member

vbreuss commented Feb 25, 2024

I wrote the Testably file system, because I had some requirements, that were not really feasible in the TestableIO testing helper. My main goal was to be as close as possible to the real file system, which is why all unit tests in Testably.Abstractions.Tests run both against the real and the mock file system (this is why I used source generators).
In order to simplify the transition, I worked in TestableIO to separate the interfaces from the wrappers and used the same interfaces.

The change you are proposing, is probably easier to implement here, even if it is still a huge change!

@vbreuss
Copy link
Member

vbreuss commented Feb 25, 2024

but we now have some unit tests which pass with native Windows and Linux FS, as well as the MockFileSystem from TestableIO 20.0.15, while they fail with Testably.Abstractions.Testing 2.6.1.

I would be really interested in these failing tests. Could you create issues for them, so that I can help investigating?

vbreuss added a commit that referenced this issue Feb 26, 2024
As a prerequisite to #460 refactor how to handle OS-specific use cases:
- Make the `Execute` class non-static and a property of the
`MockFileSystem`
- Replace all calls to the `Execute` methods to use the property on the
`MockFileSystem` instead
- Move the
[`StringComparisonMode`](https://github.com/Testably/Testably.Abstractions/blob/c42bd527e3795677136d1af787f3c5a243ff6c3f/Source/Testably.Abstractions.Testing/Storage/InMemoryLocation.cs#L9)
from `InMemoryLocation` to the `Execute` class

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
vbreuss added a commit that referenced this issue Feb 27, 2024
As a prerequisite to #460 refactor how to handle OS-specific use cases
in tests:
- Make the `Test` class non-static and a property of the
source-generated test classes
- Replace all calls to the `Test` methods to use the property instead

---------

Co-authored-by: Valentin <[email protected]>
@vbreuss
Copy link
Member

vbreuss commented Feb 28, 2024

@markusschaber
I implemented some preliminary changes, so that the Execute and Test classes that encapsulate the OS-specific behaviour in the MockFileSystem and the tests respectively, are no longer static. The main obstacle now is, to implement the Path-specific behaviour in the PathMock class.

@markusschaber
Copy link
Author

Thanks a lot. I still got no reply to my inquiry, but I intend to contribute.

@vbreuss
Copy link
Member

vbreuss commented Mar 2, 2024

Thanks a lot. I still got no reply to my inquiry, but I intend to contribute.

As a starting point, you can look at this commit, which adapts the tests to run against all platforms. The test run, but currently fail (probably due to the incorrect Path handling):
image

@markusschaber
Copy link
Author

Sorry for keeping you hanging for so long. I now have the official permission to contribute. I'm a bit under water right now, but I'll be able to work on this soon. thanks for your patience.

@vbreuss
Copy link
Member

vbreuss commented Mar 14, 2024

Thanks for the information. No worries 😄

@vbreuss
Copy link
Member

vbreuss commented Apr 29, 2024

I finished the implementation of this feature with #576.
All tests now run successfully against

  • The RealFileSystem and the native MockFileSystem (as before)
  • The simulated MockFileSystem for Linux, MacOS and Windows

I added a short explanation to the README.md

Limitations:

  • The simulation does not work on .NET Framework
  • The simulation does not work for the Testably.Abstractions.AccessControl extensions, because the ACL-related classes cannot be used (and therefore also not simulated) on Linux or MacOS.

@markusschaber: Could you please have a look, if this solves your use-case?

@vbreuss vbreuss reopened this Apr 29, 2024
Copy link

github-actions bot commented May 5, 2024

This is addressed in release v3.2.0.

@markusschaber
Copy link
Author

@vbreuss Sorry for being silent so long, I've been quite busy and distracted with other problems. I'll definitely come back to this issue, but it might take one or two more weeks.

@markusschaber
Copy link
Author

markusschaber commented Jul 15, 2024

I finally got around to test the code, and the platform abstraction part seems to work fine. I could successfully adapt our tests to run on the simulated platforms after adapting some code in our tests to consider MockFS.SimulationMode when trying to determine platform dependent behavior.

I also could implement central workarounds for our failing unit tests, so they're not a blocker anymore. This allowed me to run all platform independent test cases on all simulation targets. Apparently, the MockFS still has some problems when directories are created (or renamed to) a name ending in slash or backslash, and it also doesn't throw exceptions when calling EnumerateFileSystemInfos() on a file. I stripped down our tests for some minimal repro tests below:

FsTests.cs

If you're interested, I could try to prepare a fix and pull request for those problems.

But so far, it seems to be a success, allowing us to test (most of) our Windows specific file handling code in the Linux based CI/CD pipeline, and the Linux specific code on the Windows develomer machines. Thank you very much!

There are some other minor differences, e. G. when trying to read a directory as file, where the platforms throw different exceptions, and it seems that, sometimes, the Mock throws a different exception. Investigation pending, and low priority.

I did not yet investigate our explicit platform dependent tests, which rely on implementation details like concurrent access behaviour - Windows tends to throw Exceptions when someone tries to write, move, replace or delete a file which has open file handles (or folders containing such files), while Linux doesn't. Some of our tests verify the handling of those exceptions in our code, and I did not check yet whether they can be simulated on Linux with MockFS.

@vbreuss
Copy link
Member

vbreuss commented Jul 16, 2024

@markusschaber: Great to hear, that the solution works for you!

If you're interested, I could try to prepare a fix and pull request for those problems.

Yes, a pull request would be welcome.
Maybe create a separate issue for the failing tests, so that the comments can stay on topic?

There are some other minor differences, e. G. when trying to read a directory as file, where the platforms throw different exceptions, and it seems that, sometimes, the Mock throws a different exception. Investigation pending, and low priority.
I did not yet investigate our explicit platform dependent tests, which rely on implementation details like concurrent access behaviour - Windows tends to throw Exceptions when someone tries to write, move, replace or delete a file which has open file handles (or folders containing such files), while Linux doesn't. Some of our tests verify the handling of those exceptions in our code, and I did not check yet whether they can be simulated on Linux with MockFS.

Also here I would be interested in more details, if you can share them. There is quite some OS-dependent logic for concurrent file access in InMemoryStorage and in the InMemoryContainer.RequestAccess method, but probably some edge cases were missed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request state: released
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants