From 387cf98c410bdca8fd195b28cbe53af578698f94 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Thu, 30 Jun 2016 17:43:57 -0700 Subject: [PATCH 1/3] Add Path.GetRelativePath This change adds Path.GetRelativePath, which will calculate the relative path from one directory to another file/directory. This change also adds support for targetting netcoreapp --- dir.props | 2 +- dir.targets | 13 -- src/Common/src/System/IO/PathInternal.Unix.cs | 3 +- .../src/System/IO/PathInternal.Windows.cs | 2 + src/Common/src/System/IO/PathInternal.cs | 87 ++++++++++- .../Tests/System/IO/PathInternal.Tests.cs | 57 ++++++++ .../System/IO/PathInternal.Windows.Tests.cs | 15 ++ .../System.Runtime.Extensions.sln | 2 + .../pkg/System.Runtime.Extensions.pkgproj | 2 +- .../ref/System.Runtime.Extensions.builds | 11 ++ .../ref/System.Runtime.Extensions.cs | 3 + .../ref/System.Runtime.Extensions.csproj | 3 +- .../ref/project.json | 9 +- .../src/System.Runtime.Extensions.csproj | 6 +- .../src/System/IO/Path.Unix.cs | 5 + .../src/System/IO/Path.Windows.cs | 3 + .../src/System/IO/Path.cs | 100 +++++++++++++ .../System.Runtime.Extensions.Tests.builds | 10 ++ .../System.Runtime.Extensions.Tests.csproj | 20 ++- .../tests/System/IO/Path.GetRelativePath.cs | 138 ++++++++++++++++++ .../tests/project.json | 4 +- src/ref.builds | 2 +- 22 files changed, 464 insertions(+), 33 deletions(-) create mode 100644 src/System.Runtime.Extensions/ref/System.Runtime.Extensions.builds create mode 100644 src/System.Runtime.Extensions/tests/System/IO/Path.GetRelativePath.cs diff --git a/dir.props b/dir.props index 80aa264931dd..ecd04db42660 100644 --- a/dir.props +++ b/dir.props @@ -467,7 +467,7 @@ - netcoreapp1.1 + netcoreapp1.1 netcoreapp1.0 netcoreapp1.1 diff --git a/dir.targets b/dir.targets index 500ee75e3c18..4b3e69a0b71f 100644 --- a/dir.targets +++ b/dir.targets @@ -58,19 +58,6 @@ - - - - - - - - - - netcoreapp1.1 - - - diff --git a/src/Common/src/System/IO/PathInternal.Unix.cs b/src/Common/src/System/IO/PathInternal.Unix.cs index d3e92a1e5c55..ccf1069a287c 100644 --- a/src/Common/src/System/IO/PathInternal.Unix.cs +++ b/src/Common/src/System/IO/PathInternal.Unix.cs @@ -16,6 +16,8 @@ internal static partial class PathInternal internal static readonly int MaxComponentLength = Interop.Sys.MaxName; + internal const string ParentDirectoryPrefix = @"../"; + /// Returns a value indicating if the given path contains invalid characters. internal static bool HasIllegalCharacters(string path) { @@ -25,7 +27,6 @@ internal static bool HasIllegalCharacters(string path) internal static int GetRootLength(string path) { - PathInternal.CheckInvalidPathChars(path); return path.Length > 0 && IsDirectorySeparator(path[0]) ? 1 : 0; } diff --git a/src/Common/src/System/IO/PathInternal.Windows.cs b/src/Common/src/System/IO/PathInternal.Windows.cs index 8dd41bf354fd..079f1248ced1 100644 --- a/src/Common/src/System/IO/PathInternal.Windows.cs +++ b/src/Common/src/System/IO/PathInternal.Windows.cs @@ -45,6 +45,8 @@ internal static partial class PathInternal internal const string UncExtendedPrefixToInsert = @"?\UNC\"; internal const string UncExtendedPathPrefix = @"\\?\UNC\"; internal const string DevicePathPrefix = @"\\.\"; + internal const string ParentDirectoryPrefix = @"..\"; + internal const int MaxShortPath = 260; internal const int MaxShortDirectoryPath = 248; internal const int MaxLongPath = short.MaxValue; diff --git a/src/Common/src/System/IO/PathInternal.cs b/src/Common/src/System/IO/PathInternal.cs index 67261060329e..ee68750224aa 100644 --- a/src/Common/src/System/IO/PathInternal.cs +++ b/src/Common/src/System/IO/PathInternal.cs @@ -21,7 +21,7 @@ internal static void CheckInvalidPathChars(string path) if (path == null) throw new ArgumentNullException(nameof(path)); - if (PathInternal.HasIllegalCharacters(path)) + if (HasIllegalCharacters(path)) throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path)); } @@ -98,16 +98,95 @@ internal static StringBuilder TrimEnd(this StringBuilder builder, params char[] internal static int FindFileNameIndex(string path) { Debug.Assert(path != null); - PathInternal.CheckInvalidPathChars(path); - + CheckInvalidPathChars(path); + for (int i = path.Length - 1; i >= 0; i--) { char ch = path[i]; if (IsDirectoryOrVolumeSeparator(ch)) return i + 1; } - + return 0; // the whole path is the filename } + + /// + /// Returns true if the path ends in a directory separator. + /// + internal static bool EndsInDirectorySeparator(string path) => + !string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); + + /// + /// Get the common path length from the start of the string. + /// + internal static int GetCommonPathLength(string first, string second, bool ignoreCase) + { + int commonChars = EqualStartingCharacterCount(first, second, ignoreCase: ignoreCase); + + // If nothing matches + if (commonChars == 0) + return commonChars; + + // Or we're a full string and equal length or match to a separator + if (commonChars == first.Length + && (commonChars == second.Length || IsDirectorySeparator(second[commonChars]))) + return commonChars; + + if (commonChars == second.Length && IsDirectorySeparator(first[commonChars])) + return commonChars; + + // It's possible we matched somewhere in the middle of a segment e.g. C:\Foodie and C:\Foobar. + while (commonChars > 0 && !IsDirectorySeparator(first[commonChars - 1])) + commonChars--; + + return commonChars; + } + + /// + /// Gets the count of common characters from the left optionally ignoring case + /// + unsafe internal static int EqualStartingCharacterCount(string first, string second, bool ignoreCase) + { + if (string.IsNullOrEmpty(first) || string.IsNullOrEmpty(second)) return 0; + + int commonChars = 0; + + fixed (char* f = first) + fixed (char* s = second) + { + char* l = f; + char* r = s; + char* leftEnd = l + first.Length; + char* rightEnd = r + second.Length; + + while (l != leftEnd && r != rightEnd + && (*l == *r || (ignoreCase && char.ToUpperInvariant((*l)) == char.ToUpperInvariant((*r))))) + { + commonChars++; + l++; + r++; + } + } + + return commonChars; + } + + /// + /// Returns true if the two paths have the same root + /// + internal static bool AreRootsEqual(string first, string second, StringComparison comparisonType) + { + int firstRootLength = GetRootLength(first); + int secondRootLength = GetRootLength(second); + + return firstRootLength == secondRootLength + && string.Compare( + strA: first, + indexA: 0, + strB: second, + indexB: 0, + length: firstRootLength, + comparisonType: comparisonType) == 0; + } } } diff --git a/src/Common/tests/Tests/System/IO/PathInternal.Tests.cs b/src/Common/tests/Tests/System/IO/PathInternal.Tests.cs index 3411b75d5a53..0310cebdd89e 100644 --- a/src/Common/tests/Tests/System/IO/PathInternal.Tests.cs +++ b/src/Common/tests/Tests/System/IO/PathInternal.Tests.cs @@ -38,5 +38,62 @@ public void StartsWithOrdinal_PositiveCases(string source, string value, bool ex Assert.Equal(expected, PathInternal.StartsWithOrdinal(source, value)); Assert.Equal(expected, PathInternal.StartsWithOrdinal(new StringBuilder(source), value)); } + + [Theory, + InlineData("", "", true, 0) + InlineData("", "", false, 0) + InlineData("a", "", true, 0) + InlineData("a", "", false, 0) + InlineData("", "b", true, 0) + InlineData("", "b", false, 0) + InlineData("\0", "\0", true, 1) + InlineData("\0", "\0", false, 1) + InlineData("ABcd", "ABCD", true, 4) + InlineData("ABCD", "ABcd", true, 4) + InlineData("ABcd", "ABCD", false, 2) + InlineData("ABCD", "ABcd", false, 2) + InlineData("AB\0cd", "AB\0CD", true, 5) + InlineData("AB\0CD", "AB\0cd", true, 5) + InlineData("AB\0cd", "AB\0CD", false, 3) + InlineData("AB\0CD", "AB\0cd", false, 3) + InlineData("ABc\0", "ABC\0", true, 4) + InlineData("ABC\0", "ABc\0", true, 4) + InlineData("ABc\0", "ABC\0", false, 2) + InlineData("ABC\0", "ABc\0", false, 2) + InlineData("ABcdxyzl", "ABCDpdq", true, 4) + InlineData("ABCDxyz", "ABcdpdql", true, 4) + InlineData("ABcdxyz", "ABCDpdq", false, 2) + InlineData("ABCDxyzoo", "ABcdpdq", false, 2) + ] + public void EqualStartingCharacterCount(string first, string second, bool ignoreCase, int expected) + { + Assert.Equal(expected, PathInternal.EqualStartingCharacterCount(first, second, ignoreCase)); + } + + + [Theory, + InlineData(@"", @"", true, 0) + InlineData(@"", @"", false, 0) + InlineData(@"a", @"A", true, 1) + InlineData(@"A", @"a", true, 1) + InlineData(@"a", @"A", false, 0) + InlineData(@"A", @"a", false, 0) + InlineData(@"foo", @"foobar", true, 0) + InlineData(@"foo", @"foobar", false, 0) + InlineData(@"foo", @"foo/bar", true, 3) + InlineData(@"foo", @"foo/bar", false, 3) + InlineData(@"foo/", @"foo/bar", true, 4) + InlineData(@"foo/", @"foo/bar", false, 4) + InlineData(@"foo/bar", @"foo/bar", true, 7) + InlineData(@"foo/bar", @"foo/bar", false, 7) + InlineData(@"foo/bar", @"foo/BAR", true, 7) + InlineData(@"foo/bar", @"foo/BAR", false, 4) + InlineData(@"foo/bar", @"foo/barb", true, 4) + InlineData(@"foo/bar", @"foo/barb", false, 4) + ] + public void GetCommonPathLength(string first, string second, bool ignoreCase, int expected) + { + Assert.Equal(expected, PathInternal.GetCommonPathLength(first, second, ignoreCase)); + } } } diff --git a/src/Common/tests/Tests/System/IO/PathInternal.Windows.Tests.cs b/src/Common/tests/Tests/System/IO/PathInternal.Windows.Tests.cs index 9e92dcff15bb..ce1f99c9a7ba 100644 --- a/src/Common/tests/Tests/System/IO/PathInternal.Windows.Tests.cs +++ b/src/Common/tests/Tests/System/IO/PathInternal.Windows.Tests.cs @@ -192,4 +192,19 @@ public void FindFileNameIndexTests(string path, int expected) { Assert.Equal(expected, PathInternal.FindFileNameIndex(path)); } + + [Theory, + InlineData(@"", @"", StringComparison.OrdinalIgnoreCase, true) + InlineData(@"", @"", StringComparison.Ordinal, true) + InlineData(@"A", @"a", StringComparison.OrdinalIgnoreCase, true) + InlineData(@"A", @"a", StringComparison.Ordinal, true) + InlineData(@"C:\", @"c:\", StringComparison.OrdinalIgnoreCase, true) + InlineData(@"C:\", @"c:\", StringComparison.Ordinal, false) + ] + [PlatformSpecific(PlatformID.Windows)] + public void AreRootsEqual(string first, string second, StringComparison comparisonType, bool expected) + { + Assert.Equal(expected, PathInternal.AreRootsEqual(first, second, comparisonType)); + } + } diff --git a/src/System.Runtime.Extensions/System.Runtime.Extensions.sln b/src/System.Runtime.Extensions/System.Runtime.Extensions.sln index 91e401abeba2..26b62e86eb52 100644 --- a/src/System.Runtime.Extensions/System.Runtime.Extensions.sln +++ b/src/System.Runtime.Extensions/System.Runtime.Extensions.sln @@ -139,5 +139,7 @@ Global {845D2B72-D8A4-42E5-9BE9-17639EC4FC1A} = {1962877E-782F-499B-8468-F0F2A0692E96} {E73B023E-609E-4281-866A-3AECB1AB5D48} = {8C66E70A-65C3-443E-A23A-18A1C12972B0} {6C314C9B-3D28-4B05-9B4C-B57A00A9B3B9} = {4253F45C-FA2F-4743-9FC8-20666C416A68} + {373D255D-4749-4F26-A24A-A083E77B4471} = {4253F45C-FA2F-4743-9FC8-20666C416A68} + {9F312D76-9AF1-4E90-B3B0-815A1EC6C346} = {4253F45C-FA2F-4743-9FC8-20666C416A68} EndGlobalSection EndGlobal diff --git a/src/System.Runtime.Extensions/pkg/System.Runtime.Extensions.pkgproj b/src/System.Runtime.Extensions/pkg/System.Runtime.Extensions.pkgproj index b4546a50013b..5d2ee957e1b8 100644 --- a/src/System.Runtime.Extensions/pkg/System.Runtime.Extensions.pkgproj +++ b/src/System.Runtime.Extensions/pkg/System.Runtime.Extensions.pkgproj @@ -11,7 +11,7 @@ netcoreapp1.0;net462 - + netcoreapp1.1;net463;$(AllXamarinFrameworks) diff --git a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.builds b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.builds new file mode 100644 index 000000000000..89bc73da61ff --- /dev/null +++ b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.builds @@ -0,0 +1,11 @@ + + + + + + + netcoreapp1.1 + + + + \ No newline at end of file diff --git a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs index 6b5bf347342c..6bd35d9b9a73 100644 --- a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs +++ b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.cs @@ -799,6 +799,9 @@ public static partial class Path public static string GetTempPath() { return default(string); } public static bool HasExtension(string path) { return default(bool); } public static bool IsPathRooted(string path) { return default(bool); } +#if netcoreapp11 + public static string GetRelativePath(string relativeTo, string path) { return default(string); } +#endif } } namespace System.Net diff --git a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.csproj index e94f49d319b1..99ff56c0a3be 100644 --- a/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/ref/System.Runtime.Extensions.csproj @@ -3,7 +3,8 @@ Library - .NETStandard,Version=v1.7 + .NETStandard,Version=v1.7 + $(DefineConstants);netcoreapp11 diff --git a/src/System.Runtime.Extensions/ref/project.json b/src/System.Runtime.Extensions/ref/project.json index f516fb8d41b1..3a50a8f79ad3 100644 --- a/src/System.Runtime.Extensions/ref/project.json +++ b/src/System.Runtime.Extensions/ref/project.json @@ -1,12 +1,9 @@ { "dependencies": { - "System.Runtime": "4.0.20" + "System.Runtime": "4.1.0" }, "frameworks": { - "netstandard1.7": { - "imports": [ - "dotnet5.8" - ] - } + "netstandard1.7": { }, + "netcoreapp1.1": { } } } diff --git a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj index cc7556ea2217..107d7812696f 100644 --- a/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj +++ b/src/System.Runtime.Extensions/src/System.Runtime.Extensions.csproj @@ -186,7 +186,8 @@ Common\Interop\Windows\mincore\Interop.LookupAccountNameW.cs - + + Microsoft.Win32.Registry\src\Microsoft\Win32\Registry.cs @@ -371,7 +372,8 @@ - + + diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs b/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs index 48712719e0cd..ad6f9210b5a0 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.Unix.cs @@ -20,6 +20,8 @@ public static partial class Path private static readonly int MaxPath = Interop.Sys.MaxPath; private static readonly int MaxLongPath = MaxPath; + private static readonly bool s_isMac = Interop.Sys.GetUnixName() == "OSX"; + // Expands the given path to a fully qualified path. public static string GetFullPath(string path) { @@ -213,5 +215,8 @@ private static unsafe void GetCryptoRandomBytes(byte* bytes, int byteCount) throw new InvalidOperationException(SR.InvalidOperation_Cryptography); } } + + /// Gets whether the system is case-sensitive. + internal static bool IsCaseSensitive { get { return !s_isMac; } } } } diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs b/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs index 81aa5d8237db..b597efc54ecf 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs @@ -146,5 +146,8 @@ public static string GetPathRoot(string path) int pathRoot = PathInternal.GetRootLength(path); return pathRoot <= 0 ? string.Empty : path.Substring(0, pathRoot); } + + /// Gets whether the system is case-sensitive. + internal static bool IsCaseSensitive { get { return false; } } } } diff --git a/src/System.Runtime.Extensions/src/System/IO/Path.cs b/src/System.Runtime.Extensions/src/System/IO/Path.cs index 8be636f4e8d2..d6daad0a9437 100644 --- a/src/System.Runtime.Extensions/src/System/IO/Path.cs +++ b/src/System.Runtime.Extensions/src/System/IO/Path.cs @@ -471,5 +471,105 @@ private static unsafe void Populate83FileNameFromRandomBytes(byte* bytes, int by chars[10] = s_base32Char[(bytes[6] & 0x1F)]; chars[11] = s_base32Char[(bytes[7] & 0x1F)]; } + + /// + /// Create a relative path from one path to another. Paths will be resolved before calculating the difference. + /// Default path comparison for the active platform will be used (OrdinalIgnoreCase for Windows or Mac, Ordinal for Unix). + /// + /// The source path the output should be relative to. This path is always considered to be a directory. + /// The destination path. + /// The relative path or if the paths don't share the same root. + /// Thrown if or is null or an empty string. + public static string GetRelativePath(string relativeTo, string path) + { + return GetRelativePath(relativeTo, path, StringComparison); + } + + private static string GetRelativePath(string relativeTo, string path, StringComparison comparisonType) + { + if (string.IsNullOrEmpty(relativeTo)) throw new ArgumentNullException(nameof(relativeTo)); + if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); + Debug.Assert(comparisonType == StringComparison.Ordinal || comparisonType == StringComparison.OrdinalIgnoreCase); + + relativeTo = GetFullPath(relativeTo); + path = GetFullPath(path); + + // Need to check if the roots are different- if they are we need to return the "to" path. + if (!PathInternal.AreRootsEqual(relativeTo, path, comparisonType)) + return path; + + int commonLength = PathInternal.GetCommonPathLength(relativeTo, path, ignoreCase: comparisonType == StringComparison.OrdinalIgnoreCase); + + // If there is nothing in common they can't share the same root, return the "to" path as is. + if (commonLength == 0) + return path; + + // Trailing separators aren't significant for comparison + int relativeToLength = relativeTo.Length; + if (PathInternal.EndsInDirectorySeparator(relativeTo)) + relativeToLength--; + + bool pathEndsInSeparator = PathInternal.EndsInDirectorySeparator(path); + int pathLength = path.Length; + if (pathEndsInSeparator) + pathLength--; + + // If we have effectively the same path, return "." + if (relativeToLength == pathLength && commonLength >= relativeToLength) return "."; + + // We have the same root, we need to calculate the difference now using the + // common Length and Segment count past the length. + // + // Some examples: + // + // C:\Foo C:\Bar L3, S1 -> ..\Bar + // C:\Foo C:\Foo\Bar L6, S0 -> Bar + // C:\Foo\Bar C:\Bar\Bar L3, S2 -> ..\..\Bar\Bar + // C:\Foo\Foo C:\Foo\Bar L7, S1 -> ..\Bar + + StringBuilder sb = StringBuilderCache.Acquire(Math.Max(relativeTo.Length, path.Length)); + + // Add parent segments for segments past the common on the "from" path + if (commonLength < relativeToLength) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + + for (int i = commonLength; i < relativeToLength; i++) + { + if (PathInternal.IsDirectorySeparator(relativeTo[i])) + { + sb.Append(PathInternal.ParentDirectoryPrefix); + } + } + } + else if (PathInternal.IsDirectorySeparator(path[commonLength])) + { + // No parent segments and we need to eat the initial separator + // (C:\Foo C:\Foo\Bar case) + commonLength++; + } + + // Now add the rest of the "to" path, adding back the trailing separator + int count = pathLength - commonLength; + if (pathEndsInSeparator) + count++; + + sb.Append(path, commonLength, count); + return StringBuilderCache.GetStringAndRelease(sb); + } + + // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are + // too low in System.Runtime.Extensions to use it (no FileStream, etc.) + + /// Returns a comparison that can be used to compare file and directory names for equality. + internal static StringComparison StringComparison + { + get + { + return IsCaseSensitive ? + StringComparison.Ordinal : + StringComparison.OrdinalIgnoreCase; + } + } } } diff --git a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds index 18aa54929d33..fafccf48f98c 100644 --- a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds +++ b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds @@ -19,6 +19,16 @@ netcoreapp1.1 Windows_NT + + netcoreapp1.1 + netcoreapp1.1 + Windows_NT + + + netcoreapp1.1 + netcoreapp1.1 + Unix + diff --git a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj index 0fc7286bc0fd..833a69f90ea1 100644 --- a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj +++ b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj @@ -15,18 +15,33 @@ $(DefineConstants);netstandard17 .NETStandard,Version=v1.5 + + + ns1.7-netcoreapp1.1 + + + + + netcoreapp1.1 + + - + + + + - + + + @@ -94,6 +109,7 @@ + {1e689c1b-690c-4799-bde9-6e7990585894} System.Runtime.Extensions.CoreCLR diff --git a/src/System.Runtime.Extensions/tests/System/IO/Path.GetRelativePath.cs b/src/System.Runtime.Extensions/tests/System/IO/Path.GetRelativePath.cs new file mode 100644 index 000000000000..747d7314b8b6 --- /dev/null +++ b/src/System.Runtime.Extensions/tests/System/IO/Path.GetRelativePath.cs @@ -0,0 +1,138 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Xunit; + +namespace System.IO.Tests +{ + public static class GetRelativePathTests + { + [Theory] + [InlineData(@"C:\", @"C:\", @".")] + [InlineData(@"C:\a", @"C:\a\", @".")] + [InlineData(@"C:\A", @"C:\a\", @".")] + [InlineData(@"C:\a\", @"C:\a", @".")] + [InlineData(@"C:\", @"C:\b", @"b")] + [InlineData(@"C:\a", @"C:\b", @"..\b")] + [InlineData(@"C:\a", @"C:\b\", @"..\b\")] + [InlineData(@"C:\a\", @"C:\b", @"..\b")] + [InlineData(@"C:\a", @"C:\a\b", @"b")] + [InlineData(@"C:\a", @"C:\A\b", @"b")] + [InlineData(@"C:\a", @"C:\b\c", @"..\b\c")] + [InlineData(@"C:\a\", @"C:\a\b", @"b")] + [InlineData(@"C:\", @"D:\b", @"D:\b")] + [InlineData(@"C:\a", @"D:\b", @"D:\b")] + [InlineData(@"C:\a\", @"D:\b", @"D:\b")] + [InlineData(@"C:\ab", @"C:\a", @"..\a")] + [InlineData(@"C:\a", @"C:\ab", @"..\ab")] + [InlineData(@"C:\", @"\\LOCALHOST\Share\b", @"\\LOCALHOST\Share\b")] + [InlineData(@"\\LOCALHOST\Share\a", @"\\LOCALHOST\Share\b", @"..\b")] + [PlatformSpecific(Xunit.PlatformID.Windows)] + public static void GetRelativePath_Windows(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar), + ignoreCase: true, + ignoreLineEndingDifferences: false, + ignoreWhiteSpaceDifferences: false); + } + + [Theory] + [InlineData(@"/", @"/", @".")] + [InlineData(@"/a", @"/a/", @".")] + [InlineData(@"/a/", @"/a", @".")] + [InlineData(@"/", @"/b", @"b")] + [InlineData(@"/a", @"/b", @"../b")] + [InlineData(@"/a/", @"/b", @"../b")] + [InlineData(@"/a", @"/a/b", @"b")] + [InlineData(@"/a", @"/b/c", @"../b/c")] + [InlineData(@"/a/", @"/a/b", @"b")] + [InlineData(@"/ab", @"/a", @"../a")] + [InlineData(@"/a", @"/ab", @"../ab")] + [PlatformSpecific(Xunit.PlatformID.AnyUnix)] + public static void GetRelativePath_AnyUnix(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar)); + } + + [Theory] + [InlineData(@"/a", @"/A/", @"../A/")] + [InlineData(@"/a/", @"/A", @"../A")] + [InlineData(@"/a/", @"/A/b", @"../A/b")] + [PlatformSpecific(Xunit.PlatformID.Linux)] + public static void GetRelativePath_Linux(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar)); + } + + [Theory] + [InlineData(@"/a", @"/A/", @"../A/")] + [InlineData(@"/a/", @"/A", @"../A")] + [InlineData(@"/a/", @"/A/b", @"../A/b")] + [PlatformSpecific(Xunit.PlatformID.FreeBSD)] + public static void GetRelativePath_FreeBSD(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar)); + } + + [Theory] + [InlineData(@"/a", @"/A/", @"../A/")] + [InlineData(@"/a/", @"/A", @"../A")] + [InlineData(@"/a/", @"/A/b", @"../A/b")] + [PlatformSpecific(Xunit.PlatformID.NetBSD)] + public static void GetRelativePath_NetBSD(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar)); + } + + [Theory] + [InlineData(@"/a", @"/A/", @".")] + [InlineData(@"/a/", @"/A", @".")] + [InlineData(@"/a/", @"/A/b", @"b")] + [PlatformSpecific(Xunit.PlatformID.OSX)] + public static void GetRelativePath_Mac(string relativeTo, string path, string expected) + { + string result = Path.GetRelativePath(relativeTo, path); + Assert.Equal(expected, result); + + // Check that we get the equivalent path when the result is combined with the sources + Assert.Equal( + Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar), + Path.GetFullPath(Path.Combine(Path.GetFullPath(relativeTo), result)).TrimEnd(Path.DirectorySeparatorChar), + ignoreCase: true, + ignoreLineEndingDifferences: false, + ignoreWhiteSpaceDifferences: false); + } + } +} diff --git a/src/System.Runtime.Extensions/tests/project.json b/src/System.Runtime.Extensions/tests/project.json index b0d75a0fda64..f13cbc793813 100644 --- a/src/System.Runtime.Extensions/tests/project.json +++ b/src/System.Runtime.Extensions/tests/project.json @@ -32,7 +32,9 @@ }, "frameworks": { "netstandard1.5": {}, - "netstandard1.7": {} + "netstandard1.7": {}, + "netcoreapp1.1": {}, + "netcoreapp1.0": {} }, "supports": { "coreFx.Test.netcoreapp1.0": {}, diff --git a/src/ref.builds b/src/ref.builds index eebac73aaae8..4bf366da8cd2 100644 --- a/src/ref.builds +++ b/src/ref.builds @@ -2,7 +2,7 @@ - + OSGroup;TargetGroup From 1b02003006046ccabbed18513f7172027619c936 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 19 Sep 2016 15:36:52 -0700 Subject: [PATCH 2/3] Address feedback, remove workarounds The workarounds are no longer needed and fail with the dir.targets workaround removed. --- .../tests/System.Collections.Concurrent.Tests.csproj | 6 ------ .../tests/System.Collections.NonGeneric.Tests.csproj | 6 ------ .../tests/System.Collections.Specialized.Tests.csproj | 6 ------ .../tests/System.Collections.Tests.csproj | 6 ------ src/System.Runtime.Extensions/ref/project.json | 2 +- src/System.Runtime.Extensions/tests/project.json | 3 +-- 6 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/System.Collections.Concurrent/tests/System.Collections.Concurrent.Tests.csproj b/src/System.Collections.Concurrent/tests/System.Collections.Concurrent.Tests.csproj index 74b106b089d4..bd2a5c562d52 100644 --- a/src/System.Collections.Concurrent/tests/System.Collections.Concurrent.Tests.csproj +++ b/src/System.Collections.Concurrent/tests/System.Collections.Concurrent.Tests.csproj @@ -107,11 +107,5 @@ - - - - - - \ No newline at end of file diff --git a/src/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj b/src/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj index 0b897dd0ef28..5e23c9680370 100644 --- a/src/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj +++ b/src/System.Collections.NonGeneric/tests/System.Collections.NonGeneric.Tests.csproj @@ -75,11 +75,5 @@ - - - - - - \ No newline at end of file diff --git a/src/System.Collections.Specialized/tests/System.Collections.Specialized.Tests.csproj b/src/System.Collections.Specialized/tests/System.Collections.Specialized.Tests.csproj index db0aa0f87ba1..640e6cf37544 100644 --- a/src/System.Collections.Specialized/tests/System.Collections.Specialized.Tests.csproj +++ b/src/System.Collections.Specialized/tests/System.Collections.Specialized.Tests.csproj @@ -111,11 +111,5 @@ - - - - - - \ No newline at end of file diff --git a/src/System.Collections/tests/System.Collections.Tests.csproj b/src/System.Collections/tests/System.Collections.Tests.csproj index 44b7d1d735be..f4261cbb9b7b 100644 --- a/src/System.Collections/tests/System.Collections.Tests.csproj +++ b/src/System.Collections/tests/System.Collections.Tests.csproj @@ -164,11 +164,5 @@ - - - - - - \ No newline at end of file diff --git a/src/System.Runtime.Extensions/ref/project.json b/src/System.Runtime.Extensions/ref/project.json index 3a50a8f79ad3..c27a054d9a07 100644 --- a/src/System.Runtime.Extensions/ref/project.json +++ b/src/System.Runtime.Extensions/ref/project.json @@ -1,6 +1,6 @@ { "dependencies": { - "System.Runtime": "4.1.0" + "System.Runtime": "4.3.0-beta-24516-03" }, "frameworks": { "netstandard1.7": { }, diff --git a/src/System.Runtime.Extensions/tests/project.json b/src/System.Runtime.Extensions/tests/project.json index f13cbc793813..d2db836c5108 100644 --- a/src/System.Runtime.Extensions/tests/project.json +++ b/src/System.Runtime.Extensions/tests/project.json @@ -33,8 +33,7 @@ "frameworks": { "netstandard1.5": {}, "netstandard1.7": {}, - "netcoreapp1.1": {}, - "netcoreapp1.0": {} + "netcoreapp1.1": {} }, "supports": { "coreFx.Test.netcoreapp1.0": {}, From 4e7e4211b4df6ecba41f05972440d9f44426ced0 Mon Sep 17 00:00:00 2001 From: Jeremy Kuhne Date: Mon, 19 Sep 2016 16:46:21 -0700 Subject: [PATCH 3/3] Disable ns 1.7 test build Test targets need updated to avoid issues when building multithreaded. See #11468. --- .../tests/System.Runtime.Extensions.Tests.builds | 9 +++++++++ .../tests/System.Runtime.Extensions.Tests.csproj | 1 + 2 files changed, 10 insertions(+) diff --git a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds index fafccf48f98c..03957ab9b63e 100644 --- a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds +++ b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.builds @@ -9,6 +9,14 @@ netcoreapp1.0;net462 Windows_NT + netcoreapp1.1 netcoreapp1.1 diff --git a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj index 833a69f90ea1..05a6b42cb48e 100644 --- a/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj +++ b/src/System.Runtime.Extensions/tests/System.Runtime.Extensions.Tests.csproj @@ -15,6 +15,7 @@ $(DefineConstants);netstandard17 .NETStandard,Version=v1.5 + ns1.7-netcoreapp1.1