diff --git a/.github/workflows/codeql-js-analysis.yml b/.github/workflows/codeql-js-analysis.yml index 2874b3a52..f673e8bfd 100644 --- a/.github/workflows/codeql-js-analysis.yml +++ b/.github/workflows/codeql-js-analysis.yml @@ -12,7 +12,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 permissions: actions: read contents: read diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 381a5d0f2..0bd8ecc64 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -10,7 +10,7 @@ jobs: build-and-test: strategy: matrix: - os: [windows-latest, ubuntu-20.04, macos-13] + os: [windows-latest, ubuntu-22.04, macos-13] runs-on: ${{matrix.os}} steps: - name: Check out code diff --git a/ReleaseHistory.md b/ReleaseHistory.md index d015329c5..a24057274 100644 --- a/ReleaseHistory.md +++ b/ReleaseHistory.md @@ -12,6 +12,7 @@ * BUG: Fix error `ERR997.NoValidAnalysisTargets` when scanning symbolic link files. * BUG: Fix `ERR999.UnhandledEngineException: System.IO.FileNotFoundException: Could not find file` when a file name or directory path contains URL-encoded characters. * BUG: Fix error `ERR997.NoValidAnalysisTargets` when ambiguous file/directory references are provided to `OrderedFileSpecifier`. Previously, the code required an explicit directory separator to be added to the end of a directory path. Now, the code inspects the file system and assumes that a reference to an existing directory was intended by the user (even without a trailing separator). +* BUG: Fix `ArgumentException: Illegal characters in path` in `MultithreadedAnalyzeCommandBase`when analysis target URI contains characters that are illegal in the file system. * NEW: Allow null archive uri in `MultithreadedZipArchiveArtifactProvider` (which indicates that enumerated artifact paths should not include the base archive). * NEW: Update `LogTargetParseError(IAnalysisContext, Region, string, Exception)` to include optional exception argument to denote code location where parse error occurred. * NEW: `MultithreadedAnalyzeCommandBase.EnumerateArtifact` now supports scanning into compressed (OPC) files. Initial support file extensions are: `.apk`, `.appx`, `.appxbundle`, `.docx`, `.epub`, `.jar`, `.msix`, `.msixbundle`, `.odp`, `.ods`, `.odt`, `.onepkg`, `.oxps`, `.pkg`, `.pptx`, `.unitypackage`, `.vsdx`, `.xps`, `.xlsx`, `.zip`. diff --git a/src/Sarif.Driver/Sdk/ExportConfigurationCommandBase.cs b/src/Sarif.Driver/Sdk/ExportConfigurationCommandBase.cs index 2db4ce72f..de9dd6ae7 100644 --- a/src/Sarif.Driver/Sdk/ExportConfigurationCommandBase.cs +++ b/src/Sarif.Driver/Sdk/ExportConfigurationCommandBase.cs @@ -61,7 +61,7 @@ public override int Run(ExportConfigurationOptions exportOptions) } } - string extension = Path.GetExtension(exportOptions.OutputFilePath); + string extension = FileSystem.PathGetExtension(exportOptions.OutputFilePath); if (extension.Equals(".xml", StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Sarif.Driver/Sdk/ExportRulesMetadataCommandBase.cs b/src/Sarif.Driver/Sdk/ExportRulesMetadataCommandBase.cs index a2dd2d022..50a200f36 100644 --- a/src/Sarif.Driver/Sdk/ExportRulesMetadataCommandBase.cs +++ b/src/Sarif.Driver/Sdk/ExportRulesMetadataCommandBase.cs @@ -27,7 +27,7 @@ public override int Run(ExportRulesMetadataOptions exportOptions) string format = ""; string outputFilePath = exportOptions.OutputFilePath; - string extension = Path.GetExtension(outputFilePath); + string extension = FileSystem.PathGetExtension(outputFilePath); switch (extension) { diff --git a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs index 2e6447157..24b20b8fa 100644 --- a/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs +++ b/src/Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBase.cs @@ -106,7 +106,7 @@ public virtual int Run(TOptions options, ref TContext globalContext) else { string etlFilePath = - Path.GetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase) + FileSystem.PathGetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase) ? $"{Path.GetFileNameWithoutExtension(globalContext.EventsFilePath)}.etl" : globalContext.EventsFilePath; @@ -244,7 +244,7 @@ private int Run(TContext globalContext) globalContext.Logger.AnalysisStopped(globalContext.RuntimeErrors); disposableLogger?.Dispose(); - if (Path.GetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase)) + if (FileSystem.PathGetExtension(globalContext.EventsFilePath).Equals(".csv", StringComparison.OrdinalIgnoreCase)) { var dumpEventsCommand = new DumpEventsCommand(); @@ -679,7 +679,8 @@ private async Task EnumerateArtifact(IEnumeratedArtifact artifact, TContex return false; } - string extension = Path.GetExtension(filePath); + string extension = FileSystem.PathGetExtension(filePath); + if (artifact.Uri.IsAbsoluteUri && string.IsNullOrEmpty(artifact.Uri.Query) && globalContext.OpcFileExtensions.Contains(extension)) @@ -940,7 +941,7 @@ protected virtual TContext InitializeConfiguration(string configurationFileName, if (string.IsNullOrEmpty(configurationFileName)) { return context; } - string extension = Path.GetExtension(configurationFileName); + string extension = FileSystem.PathGetExtension(configurationFileName); var configuration = new PropertiesDictionary(); if (extension.Equals(".xml", StringComparison.OrdinalIgnoreCase)) diff --git a/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs b/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs index 32d1e193d..3920eb6c6 100644 --- a/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs +++ b/src/Sarif.Multitool.Library/ApplyPolicyCommand.cs @@ -31,7 +31,7 @@ public int Run(ApplyPolicyOptions options) actualLog.ApplyPolicies(); - string fileName = CommandUtilities.GetTransformedOutputFileName(options); + string fileName = CommandUtilities.GetTransformedOutputFileName(FileSystem, options); WriteSarifFile(_fileSystem, actualLog, fileName, options.Minify); diff --git a/src/Sarif.Multitool.Library/CommandUtilities.cs b/src/Sarif.Multitool.Library/CommandUtilities.cs index fc819f1cf..46f395e1f 100644 --- a/src/Sarif.Multitool.Library/CommandUtilities.cs +++ b/src/Sarif.Multitool.Library/CommandUtilities.cs @@ -10,7 +10,7 @@ namespace Microsoft.CodeAnalysis.Sarif.Multitool { internal static class CommandUtilities { - internal static string GetTransformedOutputFileName(SingleFileOptionsBase options) + internal static string GetTransformedOutputFileName(IFileSystem fileSystem, SingleFileOptionsBase options) { string filePath = Path.GetFullPath(options.InputFilePath); @@ -25,7 +25,7 @@ internal static string GetTransformedOutputFileName(SingleFileOptionsBase option } const string TransformedExtension = ".transformed.sarif"; - string extension = Path.GetExtension(filePath); + string extension = fileSystem.PathGetExtension(filePath); // For an input file named MyFile.sarif, returns MyFile.transformed.sarif. if (extension.Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase)) diff --git a/src/Sarif.Multitool.Library/RewriteCommand.cs b/src/Sarif.Multitool.Library/RewriteCommand.cs index 40db2cade..f220454fd 100644 --- a/src/Sarif.Multitool.Library/RewriteCommand.cs +++ b/src/Sarif.Multitool.Library/RewriteCommand.cs @@ -38,7 +38,7 @@ public int Run(RewriteOptions options) return FAILURE; } - string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); + string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(FileSystem, options); SarifLog actualLog = null; diff --git a/src/Sarif.Multitool.Library/SarifValidationContext.cs b/src/Sarif.Multitool.Library/SarifValidationContext.cs index e46534521..fc7f54806 100644 --- a/src/Sarif.Multitool.Library/SarifValidationContext.cs +++ b/src/Sarif.Multitool.Library/SarifValidationContext.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using Newtonsoft.Json.Linq; @@ -30,8 +29,8 @@ public override bool IsValidAnalysisTarget { get { - return Path.GetExtension(CurrentTarget.Uri.GetFileName()).Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase) || - Path.GetExtension(CurrentTarget.Uri.GetFileName()).Equals(".json", StringComparison.OrdinalIgnoreCase); + return FileSystem.PathGetExtension(CurrentTarget.Uri.GetFileName()).Equals(SarifConstants.SarifFileExtension, StringComparison.OrdinalIgnoreCase) || + FileSystem.PathGetExtension(CurrentTarget.Uri.GetFileName()).Equals(".json", StringComparison.OrdinalIgnoreCase); } set { diff --git a/src/Sarif.Multitool.Library/SuppressCommand.cs b/src/Sarif.Multitool.Library/SuppressCommand.cs index 6baa9cec4..4428d3ebc 100644 --- a/src/Sarif.Multitool.Library/SuppressCommand.cs +++ b/src/Sarif.Multitool.Library/SuppressCommand.cs @@ -41,7 +41,7 @@ public int Run(SuppressOptions options) options.ExpiryInDays, options.Status).VisitSarifLog(currentSarifLog); - string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(options); + string actualOutputPath = CommandUtilities.GetTransformedOutputFileName(FileSystem, options); if (options.SarifOutputVersion == SarifVersion.OneZeroZero) { var visitor = new SarifCurrentToVersionOneVisitor(); diff --git a/src/Sarif/FileSystem.cs b/src/Sarif/FileSystem.cs index 515538578..64b5b0558 100644 --- a/src/Sarif/FileSystem.cs +++ b/src/Sarif/FileSystem.cs @@ -488,5 +488,19 @@ public string PathGetFileNameWithoutExtension(string path) { return Path.GetFileNameWithoutExtension(path); } + + /// + /// Returns the extension of the given path. The returned value includes the period (".") character of the extension + /// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp". + /// + /// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older + /// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw. + /// + /// The path to extract the extension from + /// The file extension or null if the given path is null or if the given path does not include an extension. + public string PathGetExtension(string path) + { + return SarifUtilities.PathGetExtension(path); + } } } diff --git a/src/Sarif/IFileSystem.cs b/src/Sarif/IFileSystem.cs index 0f763b57f..690e5346e 100644 --- a/src/Sarif/IFileSystem.cs +++ b/src/Sarif/IFileSystem.cs @@ -385,5 +385,16 @@ public interface IFileSystem /// The string returned by , minus the last period (.) and all characters following it. /// string PathGetFileNameWithoutExtension(string path); + + /// + /// Returns the extension of the given path. The returned value includes the period (".") character of the extension + /// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp". + /// + /// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older + /// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw. + /// + /// The path to extract the extension from + /// The file extension or null if the given path is null or if the given path does not include an extension. + string PathGetExtension(string path); } } diff --git a/src/Sarif/SarifUtilities.cs b/src/Sarif/SarifUtilities.cs index 1ca52d52f..b204b8b62 100644 --- a/src/Sarif/SarifUtilities.cs +++ b/src/Sarif/SarifUtilities.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Resources; using System.Text; using System.Text.RegularExpressions; @@ -210,5 +211,51 @@ internal static Encoding GetEncodingFromName(string encodingName) return null; } } + + /// + /// Returns the extension of the given path. The returned value includes the period (".") character of the extension + /// except when you have a terminal period when you get String.Empty, such as ".exe" or ".cpp". + /// + /// While the latest version of Path.GetExtension will not throw if the path contains any illegal characters, older + /// versions of .NET, some of which are still in use, will. This method acts like the newer version and will not throw. + /// + /// The path to extract the extension from + /// The file extension or null if the given path is null or if the given path does not include an extension. + internal static string PathGetExtension(string path) + { + if (path == null) + { + return null; + } + + // This function was copied from https://referencesource.microsoft.com/#mscorlib/system/io/path.cs,f424e433705aeb09 + // with one change. The following line was commented out so that this method will not throw on + // illegal characters + // CheckInvalidPathChars(path); + + int length = path.Length; + for (int i = length; --i >= 0;) + { + char ch = path[i]; + if (ch == '.') + { + if (i != length - 1) + { + return path.Substring(i, length - i); + } + else + { + return string.Empty; + } + } + + if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar) + { + break; + } + } + + return string.Empty; + } } } diff --git a/src/Test.FunctionalTests.Sarif/Multitool/BaselineOptionTests.cs b/src/Test.FunctionalTests.Sarif/Multitool/BaselineOptionTests.cs index 702bfd99e..c78da7d35 100644 --- a/src/Test.FunctionalTests.Sarif/Multitool/BaselineOptionTests.cs +++ b/src/Test.FunctionalTests.Sarif/Multitool/BaselineOptionTests.cs @@ -116,7 +116,7 @@ protected override string ConstructTestOutputFromInputResource(string inputResou RuleKindOption = AllRuleKinds// new List() { RuleKind.Sarif }, }; - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.DirectoryExists(inputLogDirectory)).Returns(true); mockFileSystem.Setup(x => x.DirectoryGetDirectories(It.IsAny())).Returns(Array.Empty()); diff --git a/src/Test.FunctionalTests.Sarif/Multitool/ValidateCommandTests.cs b/src/Test.FunctionalTests.Sarif/Multitool/ValidateCommandTests.cs index 1bed00904..e5278c19a 100644 --- a/src/Test.FunctionalTests.Sarif/Multitool/ValidateCommandTests.cs +++ b/src/Test.FunctionalTests.Sarif/Multitool/ValidateCommandTests.cs @@ -513,7 +513,7 @@ protected override string ConstructTestOutputFromInputResource(string inputResou RuleKindOption = new List() { RuleKind.Gh, RuleKind.Sarif }, }; - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny())).Returns(1); mockFileSystem.Setup(x => x.DirectoryExists(inputLogDirectory)).Returns(true); diff --git a/src/Test.UnitTests.Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBaseTests.cs b/src/Test.UnitTests.Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBaseTests.cs index e1f031266..d3395e4ad 100644 --- a/src/Test.UnitTests.Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBaseTests.cs +++ b/src/Test.UnitTests.Sarif.Driver/Sdk/MultithreadedAnalyzeCommandBaseTests.cs @@ -873,23 +873,13 @@ public void AnalyzeCommand_EncodedPaths() var uri = new Uri(path, UriKind.Absolute); string content = "foo foo"; - var mockFileSystem = new Mock(); - mockFileSystem.Setup(x => x.IsSymbolicLink(path)).Returns(false); - mockFileSystem.Setup(x => x.FileStreamLength(path)).Returns(content.Length); - mockFileSystem.Setup(x => x.FileInfoLength(path)).Returns(content.Length); - mockFileSystem.Setup(x => x.FileReadAllText(path)).Returns(content); - mockFileSystem.Setup(x => x.FileOpenRead(path)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(content))); + IFileSystem mockFileSystem = MockFactory.MakeMockFileSystem(path, content); - var target = new EnumeratedArtifact(mockFileSystem.Object) + var target = new EnumeratedArtifact(mockFileSystem) { Uri = uri }; - var options = new TestAnalyzeOptions - { - PluginFilePaths = new[] { typeof(TestRule).Assembly.FullName }, - }; - var properties = new PropertiesDictionary(); properties.SetProperty(TestRule.Behaviors, TestRuleBehaviors.LogError); @@ -910,6 +900,31 @@ public void AnalyzeCommand_EncodedPaths() sarifLog.Runs[0].Results.Count.Should().Be(1); } + [Fact] + public void AnalyzeCommand_IllegalPathCharInURL() + { + var sarifOutput = new StringBuilder(); + var command = new TestMultithreadedAnalyzeCommand(); + using var writer = new StringWriter(sarifOutput); + var logger = new SarifLogger(writer, + run: new Run { Tool = command.Tool }, + levels: BaseLogger.ErrorWarningNote, + kinds: BaseLogger.Fail); + + var target = new EnumeratedArtifact(FileSystem.Instance) { Uri = new Uri("http://example.com/sometest/bad\"characters\"path.txt"), Contents = "fake content" }; + + var context = new TestAnalysisContext + { + TargetsProvider = new ArtifactProvider(new[] { target }), + FailureLevels = BaseLogger.ErrorWarningNote, + ResultKinds = BaseLogger.Fail, + Logger = logger, + }; + + int result = command.Run(options: null, ref context); + result.Should().Be(0); + } + [Fact] [Trait(TestTraits.WindowsOnly, "true")] public void AnalyzeCommand_TracesInMemory() @@ -1096,7 +1111,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_EndToEndMultithreadedAn mockStream.Setup(m => m.CanSeek).Returns(true); mockStream.Setup(m => m.ReadByte()).Returns('a'); - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny())).Returns(2048); mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); mockFileSystem.Setup(x => x.DirectoryGetFiles(It.IsAny(), specifier)).Returns(files); @@ -1264,7 +1279,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_TargetFileSizeTestCases mockStream.Setup(m => m.CanSeek).Returns(true); mockStream.Setup(m => m.ReadByte()).Returns('a'); - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); if (testCase.actualFileContent != null) { @@ -1347,7 +1362,7 @@ public void MultithreadedMultithreadedAnalyzeCommandBase_ErrorWhenHashing() mockStream.Setup(m => m.ReadByte()).Returns('a'); mockStream.Setup(m => m.Seek(It.IsAny(), It.IsAny())).Throws(new IOException()); - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny())).Returns(2048); mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); mockFileSystem.Setup(x => x.DirectoryGetFiles(It.IsAny(), specifier)).Returns(files); @@ -1491,8 +1506,7 @@ public void MultithreadedAnalyzeCommandBase_LoadConfigurationFile(string configV OutputFilePath = "", }; - var mockFileSystem = new Mock(); - + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.FileExists(It.IsAny())).Returns(defaultFileExists); var command = new TestMultithreadedAnalyzeCommand(mockFileSystem.Object); @@ -2157,8 +2171,7 @@ private static IFileSystem CreateDefaultFileSystemForResultsCaching(IList(); - + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.DirectoryExists(It.IsAny())).Returns(true); mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny())).Returns(2048); mockFileSystem.Setup(x => x.DirectoryEnumerateFiles(It.IsAny())).Returns(new string[0]); diff --git a/src/Test.UnitTests.Sarif.Multitool.Library/ValidateCommandTests.cs b/src/Test.UnitTests.Sarif.Multitool.Library/ValidateCommandTests.cs index 6779524e3..942045e15 100644 --- a/src/Test.UnitTests.Sarif.Multitool.Library/ValidateCommandTests.cs +++ b/src/Test.UnitTests.Sarif.Multitool.Library/ValidateCommandTests.cs @@ -42,7 +42,7 @@ public void ValidateCommand_AcceptsTargetFileWithSpaceInName() string logFilePath = Path.Combine(LogFileDirectoryWithSpace, LogFileName); - var mockFileSystem = new Mock(); + Mock mockFileSystem = MockFactory.MakeMockFileSystem(); mockFileSystem.Setup(x => x.FileInfoLength(It.IsAny())).Returns(1024); mockFileSystem.Setup(x => x.DirectoryExists(LogFileDirectoryWithSpace)).Returns(true); mockFileSystem.Setup(x => x.DirectoryEnumerateFiles(LogFileDirectoryWithSpace, It.IsAny(), SearchOption.TopDirectoryOnly)).Returns(new[] { LogFileName }); diff --git a/src/Test.UnitTests.Sarif/FileSystemTests.cs b/src/Test.UnitTests.Sarif/FileSystemTests.cs index 4dff5773e..65fd5ba61 100644 --- a/src/Test.UnitTests.Sarif/FileSystemTests.cs +++ b/src/Test.UnitTests.Sarif/FileSystemTests.cs @@ -74,5 +74,33 @@ private void CleanupDirectoryOrFile(string[] paths) } } } + + [Fact] + public void PathGetExtension_ShouldExtractExtension() + { + FileSystem.Instance.PathGetExtension("test.txt").Should().Be(".txt"); + FileSystem.Instance.PathGetExtension("C:\\test.abcde").Should().Be(".abcde"); + FileSystem.Instance.PathGetExtension("C:\\some_dir\\test.yft").Should().Be(".yft"); + FileSystem.Instance.PathGetExtension("http://example.com/some.file.ext").Should().Be(".ext"); + FileSystem.Instance.PathGetExtension("some.other.dir/extensionless").Should().Be(string.Empty); + FileSystem.Instance.PathGetExtension("yetdir\\with|pipes|file.a").Should().Be(".a"); + FileSystem.Instance.PathGetExtension("yetdir\\with|pipes|file.a.").Should().Be(string.Empty); + FileSystem.Instance.PathGetExtension("yetdir\\with.pipes|file").Should().Be(".pipes|file"); + FileSystem.Instance.PathGetExtension("yetdir\\with.pipes.file").Should().Be(".file"); + FileSystem.Instance.PathGetExtension("yet.another.dir\\with.pipes|file").Should().Be(".pipes|file"); + FileSystem.Instance.PathGetExtension("yet.another.dir\\with|pipes|file").Should().Be(string.Empty); + + const string libTest1 = "call_library_version.txt"; + const string libTest2 = "C:\\test.library_version"; + const string libTest3 = "library_version\\test.file"; + const string libTest4 = "libarary.version\\test.file"; + const string libTest5 = "library.version\\test"; + + FileSystem.Instance.PathGetExtension(libTest1).Should().Be(Path.GetExtension(libTest1)); + FileSystem.Instance.PathGetExtension(libTest2).Should().Be(Path.GetExtension(libTest2)); + FileSystem.Instance.PathGetExtension(libTest3).Should().Be(Path.GetExtension(libTest3)); + FileSystem.Instance.PathGetExtension(libTest4).Should().Be(Path.GetExtension(libTest4)); + FileSystem.Instance.PathGetExtension(libTest5).Should().Be(Path.GetExtension(libTest5)); + } } } diff --git a/src/Test.UnitTests.Sarif/MockFactory.cs b/src/Test.UnitTests.Sarif/MockFactory.cs deleted file mode 100644 index 14618d738..000000000 --- a/src/Test.UnitTests.Sarif/MockFactory.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; - -using Moq; - -namespace Microsoft.CodeAnalysis.Sarif -{ - internal static class MockFactory - { - public static IFileSystem MakeMockFileSystem(string fileName, string fileText = null, string[] fileLines = null) - { - var mock = new Mock(MockBehavior.Strict); - mock.Setup(fs => fs.FileExists(It.IsAny())).Returns((string s) => s.Equals(fileName, StringComparison.OrdinalIgnoreCase)); - mock.Setup(fs => fs.PathGetFullPath(It.IsAny())).Returns((string path) => path); - mock.Setup(fs => fs.FileReadAllText(fileName)).Returns(fileText ?? string.Join(Environment.NewLine, fileLines)); - mock.Setup(fs => fs.FileReadAllLines(fileName)).Returns(fileLines ?? fileText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)); - return mock.Object; - } - } -} diff --git a/src/Test.UnitTests.Sarif.Driver/Sdk/MockFactory.cs b/src/Test.Utilities.Sarif/MockFactory.cs similarity index 58% rename from src/Test.UnitTests.Sarif.Driver/Sdk/MockFactory.cs rename to src/Test.Utilities.Sarif/MockFactory.cs index 15288bbb7..bfb42ffb3 100644 --- a/src/Test.UnitTests.Sarif.Driver/Sdk/MockFactory.cs +++ b/src/Test.Utilities.Sarif/MockFactory.cs @@ -3,22 +3,53 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Text; using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis.Sarif.Driver; + using Moq; using Match = System.Text.RegularExpressions.Match; // Avoid ambiguity with Moq.Match; -namespace Microsoft.CodeAnalysis.Sarif.Driver +namespace Microsoft.CodeAnalysis.Sarif { internal static class MockFactory { + public static Mock MakeMockFileSystem(bool strict = false) + { + var mock = new Mock(strict ? MockBehavior.Strict : MockBehavior.Default); + + mock.Setup(fs => fs.PathGetFullPath(It.IsAny())).Returns((string path) => path); + mock.Setup(fs => fs.PathGetExtension(It.IsAny())).Returns((string path) => SarifUtilities.PathGetExtension(path)); + + return mock; + } + public static IFileSystem MakeMockFileSystem(string fileName, string[] fileContents) { - var mock = new Mock(MockBehavior.Strict); + Mock mock = MakeMockFileSystem(true); + mock.Setup(fs => fs.FileExists(It.IsAny())).Returns((string s) => s.Equals(fileName, StringComparison.OrdinalIgnoreCase)); mock.Setup(fs => fs.PathGetFullPath(It.IsAny())).Returns((string path) => path); mock.Setup(fs => fs.FileReadAllLines(fileName)).Returns(fileContents); + + return mock.Object; + } + + public static IFileSystem MakeMockFileSystem(string fileName, string fileText) + { + Mock mock = MakeMockFileSystem(true); + + mock.Setup(fs => fs.FileExists(It.IsAny())).Returns((string s) => s.Equals(fileName, StringComparison.OrdinalIgnoreCase)); + mock.Setup(fs => fs.FileInfoLength(fileName)).Returns(fileText.Length); + mock.Setup(fs => fs.FileReadAllText(fileName)).Returns(fileText); + mock.Setup(fs => fs.FileReadAllLines(fileName)).Returns(fileText.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)); + mock.Setup(fs => fs.FileStreamLength(fileName)).Returns(fileText.Length); + mock.Setup(fs => fs.IsSymbolicLink(fileName)).Returns(false); + mock.Setup(fs => fs.FileOpenRead(fileName)).Returns(new MemoryStream(Encoding.UTF8.GetBytes(fileText))); + return mock.Object; } diff --git a/src/Test.Utilities.Sarif/Test.Utilities.Sarif.csproj b/src/Test.Utilities.Sarif/Test.Utilities.Sarif.csproj index 931f7a810..b544bc7c0 100644 --- a/src/Test.Utilities.Sarif/Test.Utilities.Sarif.csproj +++ b/src/Test.Utilities.Sarif/Test.Utilities.Sarif.csproj @@ -11,9 +11,10 @@ VisualStudio SDK, and mitigate risk by limiting nesting depth. --> + + -