/// <summary> /// Returns the subst source and subst target under which the test is running. /// </summary> /// <param name="substSource">Subst source.</param> /// <param name="substTarget">Subst target.</param> /// <returns>True if the test is running under subst.</returns> /// <remarks> /// Unit tests are run during BuildXL selfhost. The latter most likely runs under subst, which /// in turn, essentially, makes unit tests run under subst as well. If a unit test executes a pip that calls /// a function like <code>GetFinalPathNameByHandle</code>, then, without directory translation, /// instead of returning the substed path, the function returns the real path. This real path /// can cause dependency access violation for the pip because the file access manifest for the pip may only /// contain substed paths. /// /// For example if <code>B:\</code> is the subst target and <code>E:\Repos\BuildXL</code> is the subst source. All paths, /// based on <see cref="TemporaryDirectory"/>, that are specified for a pip executed by a unit test have <code>B:\</code> as the root. /// If the pip calls <code>GetFinalPathNameByHandle</code> on <code>B:\file</code>, then the resulting path will be /// <code>E:\Repos\BuildXL\file</code>. Further, if the pip reads <code>E:\Repos\BuildXL\file</code>, then there will be /// read violation because the path <code>E:\Repos\BuildXL\file</code> is not present anywhere in the manifest. /// /// This method calls <code>GetFinalPathNameByHandle</code> on <see cref="TemporaryDirectory"/> to infer /// <paramref name="substSource"/> and <paramref name="substTarget"/>. Since <code>GetFinalPathNameByHandle</code> /// is not implemented on non-Windows (and there is currently no subst on non-Windows), this method simply returns /// false on non-Windows. /// /// CAUTION: /// This method only works if the unit test itself is called in undetoured environment. Or, in terms of /// BuildXL Script for selfhost, the unit test itself is executed using <code>Sdk.Managed.Testing.XUnit.UnsafeUnDetoured</code> framework. /// Recall that this method calls <code>GetFinalPathNameByHandle</code> internally. If it is called in a detoured environment, /// then the detoured version of <code>GetFinalPathNameByHandle</code> will be called, and that detoured version takes into account /// the subst specified by the selfhost via the directory translation. Thus, for the above example, calling the detoured version on /// <code>B:\file</code> returns <code>B:\file</code> because the directory translation translated <code>E:\Repos\BuildXL\file</code> /// to <code>B:\file</code>. /// /// BuildXL unit tests that involve running Detours, e.g., integration tests and detours tests, are all run in undetoured environment. /// </remarks> protected bool TryGetSubstSourceAndTarget(out string substSource, out string substTarget) { substSource = null; substTarget = null; if (OperatingSystemHelper.IsUnixOS) { // There is currently no subst in non-Windows OS. return(false); } OpenFileResult directoryOpenResult = FileUtilities.TryOpenDirectory( TemporaryDirectory, FileShare.Read | FileShare.Write | FileShare.Delete, out SafeFileHandle directoryHandle); XAssert.IsTrue(directoryOpenResult.Succeeded); string directoryHandlePath = FileUtilities.GetFinalPathNameByHandle(directoryHandle, volumeGuidPath: false); if (!string.Equals(TemporaryDirectory, directoryHandlePath, StringComparison.OrdinalIgnoreCase)) { string commonPath = TemporaryDirectory.Substring(2); // Include '\' of '<Drive>:\' for searching. substTarget = TemporaryDirectory.Substring(0, 3); // Include '\' of '<Drive>:\' in the substTarget. int commonIndex = directoryHandlePath.IndexOf(commonPath, 0, StringComparison.OrdinalIgnoreCase); if (commonIndex == -1) { substTarget = null; } else { substSource = directoryHandlePath.Substring(0, commonIndex + 1); } } return(!string.IsNullOrWhiteSpace(substSource) && !string.IsNullOrWhiteSpace(substTarget)); }