// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if FEATURE_FILE_TRACKER

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;

#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
using Microsoft.CodeAnalysis.BuildTasks;
#endif

using Xunit;
using BackEndNativeMethods = Microsoft.Build.BackEnd.NativeMethods;

// PLEASE NOTE: This is a UNICODE file as it contains UNICODE characters!

#nullable disable

namespace Microsoft.Build.UnitTests.FileTracking
{
    public sealed class FileTrackerTests : IDisposable
    {
        private static string s_defaultFileTrackerPathUnquoted;
        private static string s_defaultFileTrackerPath;
        private static string s_defaultTrackerPath;

        private static string s_oldPath;

        private static string s_cmd32Path;
        private static string s_cmd64Path;

        public FileTrackerTests()
        {
            if (NativeMethodsShared.IsUnixLike)
            {
                return; // "FileTracker is not supported under Unix"
            }

            s_defaultFileTrackerPathUnquoted = FileTracker.GetFileTrackerPath(ExecutableType.SameAsCurrentProcess);
            s_defaultFileTrackerPath = "\"" + s_defaultFileTrackerPathUnquoted + "\"";
            s_defaultTrackerPath = FileTracker.GetTrackerPath(ExecutableType.SameAsCurrentProcess);

            s_cmd32Path = (IntPtr.Size == sizeof(int))
                ? Environment.ExpandEnvironmentVariables(@"%windir%\System32\cmd.exe")
                : Environment.ExpandEnvironmentVariables(@"%windir%\syswow64\cmd.exe");

            s_cmd64Path = (IntPtr.Size == sizeof(int))
                ? Environment.ExpandEnvironmentVariables(@"%windir%\sysnative\cmd.exe")
                : Environment.ExpandEnvironmentVariables(@"%windir%\System32\cmd.exe");

            // blank out the path so that we know we're not inadvertently depending on it.
            s_oldPath = Environment.GetEnvironmentVariable("PATH");

            if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix)
            {
                Environment.SetEnvironmentVariable("PATH", "/sbin:/bin");
            }
            else
            {
                Environment.SetEnvironmentVariable(
                    "PATH",
                    Environment.ExpandEnvironmentVariables("%windir%\\system32;%windir%"));
            }

#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/649
            // Call StopTrackingAndCleanup here, just in case one of the unit tests failed before it called it
            // In real code StopTrackingAndCleanup(); would always be in a finally {} block.
            FileTracker.StopTrackingAndCleanup();
            FileTrackerTestHelper.CleanTlogs();
            FileTracker.SetThreadCount(1);
#endif
        }

        public void Dispose()
        {
            // Reset PATH to its original value.
            if (s_oldPath != null)
            {
                Environment.SetEnvironmentVariable("PATH", s_oldPath);
                s_oldPath = null;
            }
            FileTrackerTestHelper.CleanTlogs();
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerHelp()
        {
            Console.WriteLine("Test: FileTracker");
            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "");

            Assert.Equal(1, exit);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerBadArg()
        {
            Console.WriteLine("Test: FileTrackerBadArg");

            int exit = FileTrackerTestHelper.RunCommandWithLog(s_defaultTrackerPath, "/q", out string log);

            Assert.Equal(1, exit);
            Assert.Contains("TRK0000", log); // bad arg
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerNoUIDll()
        {
            Console.WriteLine("Test: FileTrackerNoUIDll");
            string testDirectory = Path.Combine(Directory.GetCurrentDirectory(), "FileTrackerNoUIDll");
            string testTrackerPath = Path.Combine(testDirectory, Path.GetFileName(s_defaultTrackerPath));

            try
            {
                if (Directory.Exists(testDirectory))
                {
                    ObjectModelHelpers.DeleteDirectory(testDirectory);
                    Directory.Delete(testDirectory, true);
                }

                // create an empty directory and copy Tracker.exe -- BUT NOT TrackerUI.dll -- to
                // that directory.
                Directory.CreateDirectory(testDirectory);
                File.Copy(s_defaultTrackerPath, testTrackerPath);

                int exit = FileTrackerTestHelper.RunCommandWithLog(testTrackerPath, "/?", out string log);

                Assert.Equal(9, exit);
                // It's OK to look for the English message since that's all we're capable of printing when we can't find
                // our resource dll.
                Assert.Contains("FileTracker : ERROR : Could not load UI satellite dll 'TrackerUI.dll'", log);
            }
            finally
            {
                // Doesn't delete the directory itself, but deletes its contents.  If you try to delete the directory,
                // even after calling this method, it sometimes throws IO exceptions due to not recognizing that the
                // contents have been deleted yet.
                ObjectModelHelpers.DeleteDirectory(testDirectory);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerNonexistentRspFile()
        {
            Console.WriteLine("Test: FileTrackerNonexistentRspFile");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommandWithLog(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " @abc.rsp /c findstr /ip foo test.in", out string log);
            Console.WriteLine("");

            // missing rsp file is a non-fatal error
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");

            // but it should still be reported
            Assert.Contains("Tracker.exe:", log);
            Assert.Contains("abc.rsp", log);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerWithDll()
        {
            Console.WriteLine("Test: FileTrackerWithDll");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath);

            Assert.Equal(1, exit);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerReadOnlyTlog()
        {
            Console.WriteLine("Test: FileTrackerTlogWriteFailure");
            string tlog = "findstr.read.1.tlog";
            string trackerCommand = "/d " + s_defaultFileTrackerPath + " /c findstr /ip foo test.in";

            File.Delete(tlog);
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            try
            {
                int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, trackerCommand);
                Console.WriteLine("");
                Assert.Equal(0, exit);
                FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), tlog);

                File.SetAttributes(tlog, FileAttributes.ReadOnly);

                exit = FileTrackerTestHelper.RunCommandWithLog(s_defaultTrackerPath, trackerCommand, out string log);
                Console.WriteLine("");
                Assert.Equal(0, exit);
                Assert.Contains("FTK1011", log); // could not create new log:  the file exists.
            }
            finally
            {
                File.SetAttributes(tlog, FileAttributes.Normal);
                File.Delete(tlog);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrIn()
        {
            Console.WriteLine("Test: FileTrackerFindStrIn");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c findstr /ip foo test.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInOperations()
        {
            Console.WriteLine("Test: FileTrackerFindStrInOperations");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /o /c findstr /ip foo test.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);

            // On some OS's it calls CreateFileA as well, on Windows7 it doesn't, but it calls CreateFileW on defaultsort.nls..
            bool foundW = FileTrackerTestHelper.FindStringInTlog("CreateFileW, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            bool foundA = FileTrackerTestHelper.FindStringInTlog("CreateFileA, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.True(foundW || foundA);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInOperationsExtended()
        {
            Console.WriteLine("Test: FileTrackerFindStrInOperationsExtended");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /o /e /c findstr /ip foo test.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);

            // On some OS's it calls GetFileAttributesW as well, on Windows 2k8 R2 it doesn't
            bool foundGetFileAttributesW = FileTrackerTestHelper.FindStringInTlog("GetFileAttributesW:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            bool foundGetFileAttributesA = FileTrackerTestHelper.FindStringInTlog("GetFileAttributesA:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.True(foundGetFileAttributesW || foundGetFileAttributesA);

            // On some OS's it calls CreateFileA as well, on Windows7 it doesn't, but it calls CreateFileW on defaultsort.nls..
            bool foundCreateFileW = FileTrackerTestHelper.FindStringInTlog("CreateFileW, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            bool foundCreateFileA = FileTrackerTestHelper.FindStringInTlog("CreateFileA, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.True(foundCreateFileW || foundCreateFileA);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInOperationsExtended_AttributesOnly()
        {
            Console.WriteLine("Test: FileTrackerFindStrInOperationsExtended_AttributesOnly");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /o /a /c findstr /ip foo test.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);
            // On some OS's it calls GetFileAttributesW as well, on Windows 2k8 R2 it doesn't
            bool foundGetFileAttributesW = FileTrackerTestHelper.FindStringInTlog("GetFileAttributesW:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            bool foundGetFileAttributesA = FileTrackerTestHelper.FindStringInTlog("GetFileAttributesA:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.True(foundGetFileAttributesW || foundGetFileAttributesA);

            // On some OS's it calls CreateFileA as well, on Windows7 it doesn't, but it calls CreateFileW on defaultsort.nls..
            bool foundCreateFileW = FileTrackerTestHelper.FindStringInTlog("CreateFileW, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            bool foundCreateFileA = FileTrackerTestHelper.FindStringInTlog("CreateFileA, Desired Access=0x80000000, Creation Disposition=0x3:" + Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.True(foundCreateFileW || foundCreateFileA);
        }


#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerExtendedDirectoryTracking()
        {
            Console.WriteLine("Test: FileTrackerExtendedDirectoryTracking");

            File.Delete("directoryattributes.read.1.tlog");
            File.Delete("directoryattributes.write.1.tlog");

            string codeFile = null;
            string outputFile = Path.Combine(Path.GetTempPath(), "directoryattributes.exe");
            string codeContent = @"
using System.IO;
using System.Runtime.InteropServices;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            File.GetAttributes(Directory.GetCurrentDirectory());
            GetFileAttributes(Directory.GetCurrentDirectory());
        }

        [DllImport(""Kernel32.dll"", SetLastError = true, CharSet = CharSet.Unicode)]
        private extern static uint GetFileAttributes(string FileName);
    }
}";

            File.Delete(outputFile);

            try
            {
                codeFile = FileUtilities.GetTemporaryFileName();
                File.WriteAllText(codeFile, codeContent);
                Csc csc = new Csc();
                csc.BuildEngine = new MockEngine3();
                csc.Sources = new ITaskItem[] { new TaskItem(codeFile) };
                csc.OutputAssembly = new TaskItem(outputFile);
                csc.Execute();

                string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
                string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
                string commandArgs = "/d \"" + fileTrackerPath + "\" /o /u /e /c \"" + outputFile + "\"";

                int exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // Should track directories when '/e' is passed
                FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
                FileTrackerTestHelper.AssertFoundStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");

                File.Delete("directoryattributes.read.1.tlog");
                File.Delete("directoryattributes.write.1.tlog");

                commandArgs = "/d \"" + fileTrackerPath + "\" /o /u /a /c \"" + outputFile + "\"";

                exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // With '/a', should *not* track GetFileAttributes on directories, even though we do so on files.
                FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
                FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");

                File.Delete("directoryattributes.read.1.tlog");
                File.Delete("directoryattributes.write.1.tlog");

                commandArgs = "/d \"" + fileTrackerPath + "\" /o /u /c \"" + outputFile + "\"";

                exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // With neither '/a' nor '/e', should not do any directory tracking whatsoever
                FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesExW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
                FileTrackerTestHelper.AssertDidntFindStringInTLog("GetFileAttributesW:" + FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");

                File.Delete("directoryattributes.read.1.tlog");
                File.Delete("directoryattributes.write.1.tlog");

                commandArgs = "/d \"" + fileTrackerPath + "\" /u /e /c \"" + outputFile + "\"";

                exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // Should track directories when '/e' is passed
                FileTrackerTestHelper.AssertFoundStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");

                File.Delete("directoryattributes.read.1.tlog");
                File.Delete("directoryattributes.write.1.tlog");

                commandArgs = "/d \"" + fileTrackerPath + "\" /u /a /c \"" + outputFile + "\"";

                exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // With '/a', should *not* track GetFileAttributes on directories, even though we do so on files.
                FileTrackerTestHelper.AssertDidntFindStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");

                File.Delete("directoryattributes.read.1.tlog");
                File.Delete("directoryattributes.write.1.tlog");

                commandArgs = "/d \"" + fileTrackerPath + "\" /u /c \"" + outputFile + "\"";

                exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);

                // With neither '/a' nor '/e', should not do any directory tracking whatsoever
                FileTrackerTestHelper.AssertDidntFindStringInTLog(FileUtilities.EnsureTrailingSlash(Directory.GetCurrentDirectory()).ToUpperInvariant(), "directoryattributes.read.1.tlog");
            }
            finally
            {
                File.Delete(codeFile);
                File.Delete(outputFile);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInIncludeDuplicates()
        {
            Console.WriteLine("Test: FileTrackerFindStrInIncludeDuplicates");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            string codeFile = null;
            string outputFile = Path.Combine(Path.GetTempPath(), "readtwice.exe");
            File.Delete(outputFile);

            try
            {
                string inputPath = Path.GetFullPath("test.in");
                codeFile = FileUtilities.GetTemporaryFileName();
                string codeContent = @"using System.IO; class X { static void Main() { File.ReadAllText(@""" + inputPath + @"""); File.ReadAllText(@""" + inputPath + @"""); }}";
                File.WriteAllText(codeFile, codeContent);
                Csc csc = new Csc();
                csc.BuildEngine = new MockEngine3();
                csc.Sources = new[] { new TaskItem(codeFile) };
                csc.OutputAssembly = new TaskItem(outputFile);
                csc.Execute();

                string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
                string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
                string commandArgs = "/d \"" + fileTrackerPath + "\" /u /c \"" + outputFile + "\"";

                int exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);
            }
            finally
            {
                File.Delete(codeFile);
                File.Delete(outputFile);
            }

            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "readtwice.read.1.tlog", 2);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerDoNotRecordWriteAsRead()
        {
            Console.WriteLine("Test: FileTrackerDoNotRecordWriteAsRead");

            File.Delete("writenoread.read.1.tlog");
            File.Delete("writenoread.write.1.tlog");

            string testDirectory = Path.Combine(Directory.GetCurrentDirectory(), "FileTrackerDoNotRecordWriteAsRead");

            if (Directory.Exists(testDirectory))
            {
                ObjectModelHelpers.DeleteDirectory(testDirectory);
                Directory.Delete(testDirectory, true /* recursive delete */);
            }

            Directory.CreateDirectory(testDirectory);
            string writeFile;
            string outputFile = Path.Combine(testDirectory, "writenoread.exe");

            try
            {
                writeFile = Path.Combine(testDirectory, "test.out");
                string codeFile = Path.Combine(testDirectory, "code.cs");
                string codeContent = @"
using System.IO;
using System.Runtime.InteropServices;
class X
{
    static void Main()
    {
        FileStream f = File.Open(@""" + writeFile + @""", FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
        f.WriteByte(8);
        f.Close();
    }
}";

                File.WriteAllText(codeFile, codeContent);
                Csc csc = new Csc();
                csc.BuildEngine = new MockEngine3();
                csc.Sources = new[] { new TaskItem(codeFile) };
                csc.OutputAssembly = new TaskItem(outputFile);
                bool success = csc.Execute();

                Assert.True(success);

                string trackerPath = FileTracker.GetTrackerPath(ExecutableType.ManagedIL);
                string fileTrackerPath = FileTracker.GetFileTrackerPath(ExecutableType.ManagedIL);
                string commandArgs = "/d \"" + fileTrackerPath + "\" /o /c \"" + outputFile + "\"";

                int exit = FileTrackerTestHelper.RunCommand(trackerPath, commandArgs);
                Console.WriteLine("");
                Assert.Equal(0, exit);
            }
            finally
            {
                // Doesn't delete the directory itself, but deletes its contents.  If you try to delete the directory,
                // even after calling this method, it sometimes throws IO exceptions due to not recognizing that the
                // contents have been deleted yet.
                ObjectModelHelpers.DeleteDirectory(testDirectory);
            }

            FileTrackerTestHelper.AssertDidntFindStringInTLog("CreateFileW, Desired Access=0xc0000000, Creation Disposition=0x1:" + writeFile.ToUpperInvariant(), "writenoread.read.1.tlog");
            FileTrackerTestHelper.AssertFoundStringInTLog("CreateFileW, Desired Access=0xc0000000, Creation Disposition=0x1:" + writeFile.ToUpperInvariant(), "writenoread.write.1.tlog");
        }
#endif // ENABLE_TRACKER_TESTS

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInCommandLine()
        {
            Console.WriteLine("Test: FileTrackerFindStrInCommandLine");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /t /c findstr /ip foo test.in");
            string line = FileTrackerTestHelper.ReadLineFromFile("findstr.command.1.tlog", 1);
            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("findstr /ip foo test.in", line);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInArgumentSpaces()
        {
            Console.WriteLine("Test: FileTrackerFindStrIn");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test file.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c findstr /ip foo \"test file.in\"");
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test file.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindUnicode()
        {
            Console.WriteLine("Test: FileTrackerFindUnicode");

            File.Delete("find.read.1.tlog");
            FileTrackerTestHelper.WriteAll("t\u1EBCst.in", "foo");

            // FINDSTR.EXE doesn't support unicode, so we'll use FIND.EXE which does
            int exit = FileTrackerTestHelper.RunCommandNoStdOut(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /i . /c find /I \"\\\"foo\"\\\" t\u1EBCst.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("t\u1EBCst.in").ToUpperInvariant(), "find.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerStartProcessFindStrIn()
        {
            Console.WriteLine("Test: FileTrackerStartProcessFindStrIn");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            Process p = FileTracker.StartProcess("findstr", "/ip foo test.in", ExecutableType.Native32Bit);
            p.WaitForExit();
            int exit = p.ExitCode;
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerResponseFile()
        {
            Console.WriteLine("Test: FileTrackerResponseFile");

            File.Delete("tracker.rsp");
            FileTrackerTestHelper.WriteAll("tracker.rsp", "/d " + s_defaultFileTrackerPath + " /r jibbit");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "@tracker.rsp /c findstr /ip foo test.in");

            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("^JIBBIT",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.read.1.tlog", 1).ToUpperInvariant());
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInRootFiles()
        {
            Console.WriteLine("Test: FileTrackerFindStrInRootFiles");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /r jibbit /c findstr /ip foo test.in");

            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("^JIBBIT",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.read.1.tlog", 1).ToUpperInvariant());
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInRootFilesCommand()
        {
            Console.WriteLine("Test: FileTrackerFindStrInRootFilesCommand");

            File.Delete("findstr.read.1.tlog");
            File.Delete("findstr.command.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/t /d " + s_defaultFileTrackerPath + " /r jibbit /c findstr /ip foo test.in");

            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("^JIBBIT",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.read.1.tlog", 1).ToUpperInvariant());
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
            Assert.Equal("findstr /ip foo test.in",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.command.1.tlog", 2));
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInRootFilesSpaces()
        {
            Console.WriteLine("Test: FileTrackerFindStrInRootFilesSpaces");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /r \"jibbit goo\" /c findstr /ip foo test.in");

            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("^JIBBIT GOO",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.read.1.tlog", 1).ToUpperInvariant());
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerHelperCommandLine()
        {
            Console.WriteLine("Test: FileTrackerHelperCommandLine");

            File.Delete("findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(
                s_defaultTrackerPath,
                FileTracker.TrackerArguments(
                    "findstr",
                    "/ip foo test.in",
                    "" + s_defaultFileTrackerPathUnquoted,
                    ".",
                    "jibbit goo"));

            Console.WriteLine("");
            Assert.Equal(0, exit);
            Assert.Equal("^JIBBIT GOO",
                                   FileTrackerTestHelper.ReadLineFromFile("findstr.read.1.tlog", 1).ToUpperInvariant());
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerSortOut()
        {
            Console.WriteLine("Test: FileTrackerSortOut");

            File.Delete("sort.read.1.tlog");
            File.Delete("sort.write.1.tlog");
            File.WriteAllLines("test.in", new[] {
                                                            "bfoo",
                                                            "afoo"
                                                       });

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c sort test.in /O test.out");

            Assert.Equal(0, exit);

            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "sort.read.1.tlog");
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.out").ToUpperInvariant(), "sort.write.1.tlog");

            Assert.Equal("AFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 0).ToUpperInvariant());

            Assert.Equal("BFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 1).ToUpperInvariant());
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerSortOutIntermediate()
        {
            Console.WriteLine("Test: FileTrackerSortOutIntermediate");

            Directory.CreateDirectory("outdir");
            File.Delete("outdir\\sort.read.1.tlog");
            File.Delete("outdir\\sort.write.1.tlog");
            File.WriteAllLines("test.in", new[] {
                                                            "bfoo",
                                                            "afoo"
                                                       });

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /i outdir /c sort test.in /O test.out");

            Assert.Equal(0, exit);

            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "outdir\\sort.read.1.tlog");
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.out").ToUpperInvariant(), "outdir\\sort.write.1.tlog");

            Assert.Equal("AFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 0).ToUpperInvariant());

            Assert.Equal("BFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 1).ToUpperInvariant());
        }


        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerIntermediateDirMissing()
        {
            Console.WriteLine("Test: FileTrackerIntermediateDirMissing");

            // Make sure it really is missing
            if (Directory.Exists("outdir"))
            {
                Directory.Delete("outdir", true);
            }

            File.WriteAllLines("test.in", new[] {
                                                            "bfoo",
                                                            "afoo"
                                                       });

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /i outdir /c sort test.in /O test.out");

            Assert.Equal(0, exit);

            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "outdir\\sort.read.1.tlog");
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.out").ToUpperInvariant(), "outdir\\sort.write.1.tlog");

            Assert.Equal("AFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 0).ToUpperInvariant());

            Assert.Equal("BFOO",
                                   FileTrackerTestHelper.ReadLineFromFile("test.out", 1).ToUpperInvariant());
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInChain()
        {
            Console.WriteLine("Test: FileTrackerFindStrInChain");

            File.Delete("cmd-findstr.read.1.tlog");
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c cmd /c findstr /ip foo test.in");
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "cmd-findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInChainRepeatCommand()
        {
            Console.WriteLine("Test: FileTrackerFindStrInChainRepeatCommand");

            string[] tlogFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "cmd*-findstr.*.1.tlog", SearchOption.TopDirectoryOnly);
            foreach (string tlogFile in tlogFiles)
            {
                File.Delete(tlogFile);
            }
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c cmd /c cmd /c findstr /ip foo test.in");
            tlogFiles = Directory.GetFiles(Directory.GetCurrentDirectory(), "cmd*-findstr.read.1.tlog", SearchOption.TopDirectoryOnly);
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), tlogFiles[0]);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInX64X86ChainRepeatCommand()
        {
            Console.WriteLine("Test: FileTrackerFindStrInX64X86ChainRepeatCommand");

            if (!Environment.Is64BitOperatingSystem)
            {
                Console.WriteLine("FileTrackerFindStrInX64X86ChainRepeatCommand runs both 32-and 64-bit programs so it requires 64-bit Windows.");
                Assert.True(true);
                return;
            }

            string[] tlogFiles = Directory.GetFiles(Environment.CurrentDirectory, "cmd*-findstr.*.1.tlog", SearchOption.TopDirectoryOnly);
            foreach (string tlogFile in tlogFiles)
            {
                File.Delete(tlogFile);
            }
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c " + s_cmd64Path + " /c " + s_cmd32Path + " /c findstr /ip foo test.in");
            tlogFiles = Directory.GetFiles(Environment.CurrentDirectory, "cmd*-findstr.read.1.tlog", SearchOption.TopDirectoryOnly);
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), tlogFiles[0]);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void FileTrackerFindStrInX86X64ChainRepeatCommand()
        {
            Console.WriteLine("Test: FileTrackerFindStrInX86X64ChainRepeatCommand");

            if (!Environment.Is64BitOperatingSystem)
            {
                Console.WriteLine("FileTrackerFindStrInX86X64ChainRepeatCommand runs both 32-and 64-bit programs so it requires 64-bit Windows.");
                Assert.True(true);
                return;
            }

            string[] tlogFiles = Directory.GetFiles(Environment.CurrentDirectory, "cmd*-findstr.*.1.tlog", SearchOption.TopDirectoryOnly);
            foreach (string tlogFile in tlogFiles)
            {
                File.Delete(tlogFile);
            }
            FileTrackerTestHelper.WriteAll("test.in", "foo");

            int exit = FileTrackerTestHelper.RunCommand(s_defaultTrackerPath, "/d " + s_defaultFileTrackerPath + " /c " + s_cmd32Path + " /c " + s_cmd64Path + " /c findstr /ip foo test.in");
            tlogFiles = Directory.GetFiles(Environment.CurrentDirectory, "cmd*-findstr.read.1.tlog", SearchOption.TopDirectoryOnly);
            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), tlogFiles[0]);
        }

        [Fact]
        public void FileTrackerFileIsUnderPath()
        {
            // YES: Both refer to something under baz, so yes this is on the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\", @"c:\foo\bar\baz\"));

            // NO: Not under the path, since this *is* the path
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz", @"c:\foo\bar\baz\"));

            // NO: Not under the path, since the path is below
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz", @"c:\foo\bar\baz\"));

            // YES: Since the first parameter is a filename the extra '\' indicates we are referring to something
            // other than the actual directory - so this would be under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\", @"c:\foo\bar\baz"));

            // YES: this is under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\hobbits.tmp", @"c:\foo\bar\baz\"));

            // YES: this is under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\hobbits.tmp", @"c:\foo\bar\baz"));

            // YES: this is under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\hobbits", @"c:\foo\bar\baz\"));

            // YES: this is under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\hobbits", @"c:\foo\bar\baz"));

            // YES: this is under the path
            Assert.True(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\bootle\hobbits.tmp", @"c:\foo\bar\baz\"));

            // NO: this is not under the path
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo\bar\baz\hobbits.tmp", @"c:\boo1\far\chaz\"));

            // NO: this is not under the path
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo1.cpp", @"c:\averyveryverylongtemp\path\this\is"));

            // NO: this is not under the path
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo\rumble.cpp", @"c:\foo\rumble"));

            // NO: this is not under the path
            Assert.False(FileTracker.FileIsUnderPath(@"c:\foo\rumble.cpp", @"c:\foo\rumble\"));
        }

        [Fact]
        public void FileTrackerFileIsExcludedFromDependencies()
        {
            string applicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            string localApplicationDataPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
            string localLowApplicationDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "AppData\\LocalLow");
            // The default path to temp, used to create explicitly short and long paths
            string tempPath = Path.GetTempPath();
            // The short path to temp
            string tempShortPath = NativeMethodsShared.IsUnixLike
                                       ? tempPath
                                       : FileUtilities.EnsureTrailingSlash(
                                           NativeMethodsShared.GetShortFilePath(tempPath).ToUpperInvariant());
            // The long path to temp
            string tempLongPath = NativeMethodsShared.IsUnixLike
                                      ? tempPath
                                      : FileUtilities.EnsureTrailingSlash(
                                          NativeMethodsShared.GetLongFilePath(tempPath).ToUpperInvariant());

            // We don't want to be including these as dependencies or outputs:
            // 1. Files under %USERPROFILE%\Application Data in XP and %USERPROFILE%\AppData\Roaming in Vista and later.
            // 2. Files under %USERPROFILE%\Local Settings\Application Data in XP and %USERPROFILE%\AppData\Local in Vista and later.
            // 3. Files under %USERPROFILE%\AppData\LocalLow in Vista and later.
            // 4. Files that are in the TEMP directory (Since on XP, temp files are not
            //    located under AppData, they would not be compacted out correctly otherwise).

            // This file's NOT excluded from dependencies
            string testFile = @"c:\foo\bar\baz";
            Assert.False(FileTracker.FileIsExcludedFromDependencies(testFile));

            // This file IS excluded from dependencies
            testFile = Path.Combine(applicationDataPath, "blah.log");
            Assert.True(FileTracker.FileIsExcludedFromDependencies(testFile));

            // This file IS excluded from dependencies
            testFile = Path.Combine(localApplicationDataPath, "blah.log");
            Assert.True(FileTracker.FileIsExcludedFromDependencies(testFile));

            // This file IS excluded from dependencies
            testFile = Path.Combine(localLowApplicationDataPath, "blah.log");
            Assert.True(FileTracker.FileIsExcludedFromDependencies(testFile));

            // This file IS excluded from dependencies
            testFile = Path.Combine(tempShortPath, "blah.log");
            Assert.True(FileTracker.FileIsExcludedFromDependencies(testFile));

            // This file IS excluded from dependencies
            testFile = Path.Combine(tempLongPath, "blah.log");
            Assert.True(FileTracker.FileIsExcludedFromDependencies(testFile));
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTest1()
        {
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingTest1");

            File.WriteAllText(sourceFile, "this is a inline tracking test");

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);

            FileTracker.StopTrackingAndCleanup();
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            Assert.Equal(2, lines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);

            File.Delete(tlogWriteFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTest2()
        {
            // Do test 1 twice in a row to make sure there is no leakage
            InProcTrackingTest1();
            InProcTrackingTest1();
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTestSuspendResume()
        {
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingTestSuspendResume");

            File.WriteAllText(sourceFile, "this is a inline tracking test");

            // Nothing should be tracked following this call
            FileTracker.SuspendTracking();

            File.WriteAllText(sourceFile + "_s", "this is a inline tracking test");

            // And tracking should resume
            FileTracker.ResumeTracking();

            File.WriteAllText(sourceFile + "_r", "this is a inline tracking test");

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);

            FileTracker.StopTrackingAndCleanup();
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            Assert.Equal(3, lines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile + "_r").ToUpperInvariant(), lines[2]);

            File.Delete(tlogWriteFile);
            File.Delete(sourceFile);
            File.Delete(sourceFile + "_s");
            File.Delete(sourceFile + "_r");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTestStopBeforeWrite()
        {
            Assert.Throws<COMException>(() =>
            {
                string sourceFile = "inlinetrackingtest.txt";
                string tlogRootName = "foo_inline";
                string tlogWriteFile = $"{tlogRootName}.write.1.tlog";

                File.Delete(tlogWriteFile);
                File.Delete(sourceFile);

                FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingTestStopBeforeWrite");

                File.WriteAllText(sourceFile, "this is a inline tracking test");

                FileTracker.StopTrackingAndCleanup();

                // This should throw a COMException, since we have cleaned up
                FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
            });
        }
        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTestNotStop()
        {
            InProcTrackingTesterNoStop(1);
            // Since we didn't stop in the test, we should stop now
            // to ensure we don't leak into the other tests
            FileTracker.StopTrackingAndCleanup();
        }

        private static void InProcTrackingTesterNoStop(int iteration)
        {
            string sourceFile = $"inlinetrackingtest{iteration}.txt";
            string tlogRootName = $"foo_nonstopinline{iteration}";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogReadFile = $"{tlogRootName}.read.1.tlog";

            File.Delete(tlogWriteFile);
            File.Delete(tlogReadFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingTesterNoStop");

            File.WriteAllText(sourceFile, "this is a inline tracking test");

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);

            File.WriteAllText(sourceFile + "_s", "this is a inline tracking test - again");

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);

            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            Assert.Equal(4, lines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile + "_s").ToUpperInvariant(), lines[3]);

            File.Delete(tlogWriteFile);
            // Since we are non-stop during iteration we actually get read tlogs
            // Because of the "ReadLinesFromFile" above. However it will be empty
            // Since by default the tracker does not write entries for files that
            // do not exist - and we did delete the file being tracked on the previous
            // iteration!
            File.Delete(tlogReadFile);
            File.Delete(sourceFile);
            File.Delete(sourceFile + "_s");
        }

        private static void InProcTrackingTester(int iteration)
        {
            string sourceFile = $"inlinetrackingtest{iteration}.txt";
            string tlogRootName = $"foo_inline{iteration}";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingTester");

            File.WriteAllText(sourceFile, "this is a inline tracking test");

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);

            FileTracker.StopTrackingAndCleanup();
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            Assert.Equal(2, lines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);

            File.Delete(tlogWriteFile);
            File.Delete(sourceFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTestIteration()
        {
            for (int iter = 0; iter < 50; iter++)
            {
                InProcTrackingTester(iter);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingNonStopTestIteration()
        {
            for (int iter = 0; iter < 50; iter++)
            {
                InProcTrackingTesterNoStop(iter);
            }
            FileTracker.StopTrackingAndCleanup();
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTwoContexts()
        {
            string sourceFile = "inlinetrackingtest.txt";
            string sourceFile2 = "inlinetrackingtest2.txt";
            string sourceFile3 = "inlinetrackingtest3.txt";
            string tlogRootName = "foo_inline";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogWriteFile2 = $"{tlogRootName}2.write.1.tlog";

            File.Delete(tlogWriteFile);
            File.Delete(tlogWriteFile2);

            // Context 1
            FileTracker.StartTrackingContext(Path.GetFullPath("."), "Context1");
            File.WriteAllText(sourceFile, "this is a inline tracking test");

            // Context 2
            FileTracker.StartTrackingContext(Path.GetFullPath("."), "Context2");
            File.WriteAllText(sourceFile2, "this is a inline tracking test - in a second context");
            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName + "2");
            FileTracker.EndTrackingContext();

            // Back to context 1
            File.WriteAllText(sourceFile3, "this is a second inline tracking test in the first context");
            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
            FileTracker.EndTrackingContext();

            FileTracker.StopTrackingAndCleanup();
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            string[] lines2 = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile2);
            Assert.Equal(3, lines.Length);
            Assert.Equal(2, lines2.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile3).ToUpperInvariant(), lines[2]);
            Assert.Equal(Path.GetFullPath(sourceFile2).ToUpperInvariant(), lines2[1]);

            File.Delete(tlogWriteFile);
            File.Delete(tlogWriteFile2);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTwoContextsWithRoot()
        {
            string sourceFile = "inlinetrackingtest.txt";
            string sourceFile2 = "vi\u00FCes\u00E4tato633833475975527668.txt";
            string sourceFile3 = "inlinetrackingtest3.txt";
            string tlogRootName = "foo_inline";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogWriteFile2 = $"{tlogRootName}2.write.1.tlog";

            string rootMarker = FileTracker.FormatRootingMarker(new TaskItem(sourceFile2));
            string responseFile = FileTracker.CreateRootingMarkerResponseFile(rootMarker);

            File.Delete(tlogWriteFile);
            File.Delete(tlogWriteFile2);

            try
            {
                // Context 1
                FileTracker.StartTrackingContext(Path.GetFullPath("."), "Context1");
                File.WriteAllText(sourceFile, "this is a inline tracking test");

                // Context 2
                FileTracker.StartTrackingContextWithRoot(Path.GetFullPath("."), "Context2", responseFile);
                File.WriteAllText(sourceFile2, "this is a inline tracking test - in a second context");
                FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName + "2");
                FileTracker.EndTrackingContext();

                // Back to context 1
                File.WriteAllText(sourceFile3, "this is a second inline tracking test in the first context");
                FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
                FileTracker.EndTrackingContext();

                FileTracker.StopTrackingAndCleanup();
                string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
                string[] lines2 = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile2);
                Assert.Equal(3, lines.Length);
                Assert.Equal(3, lines2.Length);
                Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
                Assert.Equal(Path.GetFullPath(sourceFile3).ToUpperInvariant(), lines[2]);
                Assert.Equal("^" + rootMarker, lines2[1]);
                Assert.Equal(rootMarker, lines2[2]);
            }
            finally
            {
                File.Delete(tlogWriteFile);
                File.Delete(tlogWriteFile2);
                File.Delete(responseFile);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingSpawnsOutOfProcTool()
        {
            string intermediateDir = Path.GetTempPath() + @"InProcTrackingSpawnsOutOfProcTool\";

            string sourceFile = intermediateDir + @"inlinetracking1.txt";
            string commandFile = intermediateDir + @"command.bat";
            string tlogRootName = "inproc_spawn";
            string tlogWriteFile = intermediateDir + $"{tlogRootName}-cmd.write.1.tlog";
            string rootMarker = @"\\THIS\IS\MY\ROOT|\\IT\IS\COMPOUND\TOO";
            string rootMarkerRsp = intermediateDir + @"rootmarker.rsp";

            if (Directory.Exists(intermediateDir))
            {
                Directory.Delete(intermediateDir, true);
            }

            try
            {
                Directory.CreateDirectory(intermediateDir);

                File.WriteAllText(commandFile, "echo this is out of proc tracking writing stuff > \"" + sourceFile + "\"");
                File.WriteAllText(rootMarkerRsp, "/r " + rootMarker);

                FileTracker.StartTrackingContextWithRoot(intermediateDir, tlogRootName, rootMarkerRsp);

                ProcessStartInfo ps = new ProcessStartInfo("cmd.exe", "/C \"" + commandFile + "\"");
                // Clear out all environment variables
                Process cmd = Process.Start(ps);
                cmd.WaitForExit();

                FileTracker.StopTrackingAndCleanup();

                string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);

                Assert.Equal(3, lines.Length);
                Assert.Equal("^" + rootMarker, lines[1]);
                Assert.Equal(sourceFile.ToUpperInvariant(), lines[2]);
            }
            finally
            {
                if (Directory.Exists(intermediateDir))
                {
                    Directory.Delete(intermediateDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingSpawnsOutOfProcTool_OverrideEnvironment()
        {
            string intermediateDir = Path.GetTempPath() + @"InProcTrackingSpawnsOutOfProcTool_OverrideEnvironment\";

            string sourceFile = intermediateDir + @"inlinetracking1.txt";
            string commandFile = intermediateDir + @"command.bat";
            string tlogRootName = "inproc_spawn_env";
            string tlogWriteFile = intermediateDir + $"{tlogRootName}-cmd.write.1.tlog";
            string rootMarker = @"\\THIS\IS\MY\ROOT|\\IT\IS\COMPOUND\TOO";
            string rootMarkerRsp = intermediateDir + @"rootmarker.rsp";

            if (Directory.Exists(intermediateDir))
            {
                Directory.Delete(intermediateDir, true);
            }

            try
            {
                Directory.CreateDirectory(intermediateDir);

                File.WriteAllText(commandFile, "echo this is out of proc tracking writing stuff > \"" + sourceFile + "\"");
                File.WriteAllText(rootMarkerRsp, "/r " + rootMarker);

                FileTracker.StartTrackingContextWithRoot(intermediateDir, tlogRootName, rootMarkerRsp);

                ProcessStartInfo ps = new ProcessStartInfo("cmd.exe", "/C \"" + commandFile + "\"");
                ps.EnvironmentVariables["TRACKER_TOOLCHAIN"] = "MSBuild";
                ps.UseShellExecute = false;

                Process cmd = Process.Start(ps);
                cmd.WaitForExit();

                FileTracker.StopTrackingAndCleanup();

                string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);

                Assert.Equal(3, lines.Length);
                Assert.Equal("^" + rootMarker, lines[1]);
                Assert.Equal(sourceFile.ToUpperInvariant(), lines[2]);
            }
            finally
            {
                if (Directory.Exists(intermediateDir))
                {
                    Directory.Delete(intermediateDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingSpawnsToolWithTrackerResponseFile()
        {
            Console.WriteLine("Test: InProcTrackingSpawnsToolWithTrackerResponseFile");

            InProcTrackingSpawnsToolWithTracker(true);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingSpawnsToolWithTrackerNoResponseFile()
        {
            Console.WriteLine("Test: InProcTrackingSpawnsToolWithTrackerNoResponseFile");

            InProcTrackingSpawnsToolWithTracker(false);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingTwoContextsTwoEnds()
        {
            Assert.Throws<COMException>(() =>
            {
                string sourceFile = "inlinetrackingtest.txt";
                string sourceFile2 = "inlinetrackingtest2.txt";
                string tlogRootName = "foo_inline";
                string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
                string tlogWriteFile2 = $"{tlogRootName}2.write.1.tlog";

                try
                {
                    File.Delete(tlogWriteFile);
                    File.Delete(tlogWriteFile2);

                    // Context 1
                    FileTracker.StartTrackingContext(Path.GetFullPath("."), "Context1");
                    File.WriteAllText(sourceFile, "this is a inline tracking test");

                    // Context 2
                    FileTracker.StartTrackingContext(Path.GetFullPath("."), "Context2");
                    File.WriteAllText(sourceFile2, "this is a inline tracking test - in a second context");
                    FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName + "2");
                    FileTracker.EndTrackingContext();
                    // This will cause the outer context to end which will mean there is nothing in the context for the write
                    FileTracker.EndTrackingContext();

                    // There is nothing in the context to write from, we should get an exception here:
                    FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
                    FileTracker.EndTrackingContext();
                }
                finally
                {
                    FileTracker.StopTrackingAndCleanup();

                    File.Delete(tlogWriteFile);
                    File.Delete(tlogWriteFile2);
                }
            });
        }

        [Fact(Skip = "Test fails in xunit because tracker includes the PID in the log file.")]
        public void InProcTrackingStartProcessFindStrIn()
        {
            Console.WriteLine("Test: InProcTrackingStartProcessFindStrIn");
            int exit;

            try
            {
                File.Delete("findstr.read.1.tlog");
                File.Delete("InProcTrackingStartProcessFindStrIn-findstr.read.1.tlog");
                FileTrackerTestHelper.WriteAll("test.in", "foo");

                FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingStartProcessFindStrIn");
                exit = FileTrackerTestHelper.RunCommand("findstr", "/ip foo test.in");
                FileTracker.WriteContextTLogs(Path.GetFullPath("."), "inlinefind");
                FileTracker.EndTrackingContext();
            }
            finally
            {
                FileTracker.StopTrackingAndCleanup();
            }
            Console.WriteLine("");
            Assert.Equal(0, exit);
            // This line is the problem.  It seems to have been reliable in MSTest
            // but in xunit when run with other tests (NOT by itself), filetracker
            // puts a PID in the path, so this tries to open the wrong file and throws.
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "InProcTrackingStartProcessFindStrIn-findstr.read.1.tlog");
            File.Delete("findstr.read.1.tlog");
            File.Delete("InProcTrackingStartProcessFindStrIn-findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingStartProcessFindStrNullCommandLine()
        {
            Console.WriteLine("Test: InProcTrackingStartProcessFindStrNullCommandLine");

            try
            {
                FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingStartProcessFindStrIn");
                BackEndNativeMethods.STARTUP_INFO startInfo = new BackEndNativeMethods.STARTUP_INFO();
                startInfo.cb = Marshal.SizeOf<BackEndNativeMethods.STARTUP_INFO>();
                uint dwCreationFlags = BackEndNativeMethods.NORMALPRIORITYCLASS;

                startInfo.hStdError = BackEndNativeMethods.InvalidHandle;
                startInfo.hStdInput = BackEndNativeMethods.InvalidHandle;
                startInfo.hStdOutput = BackEndNativeMethods.InvalidHandle;
                startInfo.dwFlags = BackEndNativeMethods.STARTFUSESTDHANDLES;
                dwCreationFlags |= BackEndNativeMethods.CREATENOWINDOW;

                BackEndNativeMethods.SECURITY_ATTRIBUTES pSec = new BackEndNativeMethods.SECURITY_ATTRIBUTES();
                BackEndNativeMethods.SECURITY_ATTRIBUTES tSec = new BackEndNativeMethods.SECURITY_ATTRIBUTES();
                pSec.nLength = Marshal.SizeOf<BackEndNativeMethods.SECURITY_ATTRIBUTES>();
                tSec.nLength = Marshal.SizeOf<BackEndNativeMethods.SECURITY_ATTRIBUTES>();

                BackEndNativeMethods.PROCESS_INFORMATION pInfo = new BackEndNativeMethods.PROCESS_INFORMATION();

                string appName = Path.Combine(Environment.SystemDirectory, "findstr.exe");

                Assert.True(File.Exists(appName));

                string cmdLine = null;
                bool created = BackEndNativeMethods.CreateProcess(appName, cmdLine,
                                            ref pSec, ref tSec,
                                            false, dwCreationFlags,
                                            BackEndNativeMethods.NullPtr, null, ref startInfo, out pInfo);

                // We should have correctly started the process even though the command-line was null
                Assert.True(created);

                FileTracker.WriteContextTLogs(Path.GetFullPath("."), "inlinefind");
                FileTracker.EndTrackingContext();
            }
            finally
            {
                FileTracker.StopTrackingAndCleanup();
            }
            File.Delete("inlinefind.read.1.tlog");
        }


        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingStartProcessFindStrInDefaultTaskName()
        {
            Console.WriteLine("Test: InProcTrackingStartProcessFindStrInDefaultTaskName");
            int exit = 0;

            try
            {
                File.Delete("findstr.read.1.tlog");
                File.Delete("InProcTrackingStartProcessFindStrIn-findstr.read.1.tlog");
                FileTrackerTestHelper.WriteAll("test.in", "foo");

                FileTracker.StartTrackingContext(Path.GetFullPath("."), "");
                exit = FileTrackerTestHelper.RunCommand("findstr", "/ip foo test.in");
                FileTracker.EndTrackingContext();
            }
            finally
            {
                FileTracker.StopTrackingAndCleanup();
            }

            Console.WriteLine("");
            Assert.Equal(0, exit);
            FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath("test.in").ToUpperInvariant(), "findstr.read.1.tlog");

            File.Delete("findstr.read.1.tlog");
            File.Delete("InProcTrackingStartProcessFindStrIn-findstr.read.1.tlog");
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingChildThreadTrackedAuto()
        {
            FileTracker.SetThreadCount(1);
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline_parent";
            string tlogChildRootName = "InProcTrackingChildThreadTrackedAuto";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogChildWriteFile = $"{tlogChildRootName}.write.2.tlog";

            File.Delete(tlogWriteFile);
            File.Delete(tlogChildWriteFile);
            File.Delete(sourceFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingChildThreadTrackedAuto");

            File.WriteAllText(sourceFile, "parent thread\r\n");

            Thread t = new Thread(ThreadProcAutoTLog);
            t.Start();
            t.Join(); // wait for our child to complete

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName); // parent will write an explicit tlog

            FileTracker.StopTrackingAndCleanup();
            string[] writtenlines = FileTrackerTestHelper.ReadLinesFromFile(sourceFile);
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            string[] childLines = FileTrackerTestHelper.ReadLinesFromFile(tlogChildWriteFile);
            Assert.Equal(2, lines.Length);
            Assert.Equal(2, childLines.Length);
            Assert.Equal(2, writtenlines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), childLines[1]);
            Assert.Equal("parent thread", writtenlines[0]);
            Assert.Equal("child thread", writtenlines[1]);

            File.Delete(tlogWriteFile);
            File.Delete(tlogChildWriteFile);
            File.Delete(sourceFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingChildThreadTrackedManual()
        {
            FileTracker.SetThreadCount(1);
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline_parent";
            string tlogChildRootName = "foo_inline_child";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogChildWriteFile = $"{tlogChildRootName}.write.2.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingChildThreadTrackedAuto");

            File.WriteAllText(sourceFile, "parent thread\r\n");

            Thread t = new Thread(ThreadProcManualTLog);
            t.Start();
            t.Join(); // wait for our child to complete

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName); // parent will write an explicit tlog

            FileTracker.StopTrackingAndCleanup();
            string[] writtenlines = FileTrackerTestHelper.ReadLinesFromFile(sourceFile);
            string[] lines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            string[] childLines = FileTrackerTestHelper.ReadLinesFromFile(tlogChildWriteFile);
            Assert.Equal(2, lines.Length);
            Assert.Equal(2, childLines.Length);
            Assert.Equal(2, writtenlines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), lines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), childLines[1]);
            Assert.Equal("parent thread", writtenlines[0]);
            Assert.Equal("child thread", writtenlines[1]);

            File.Delete(tlogWriteFile);
            File.Delete(tlogChildWriteFile);
            File.Delete(sourceFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingChildThreadNotTracked()
        {
            FileTracker.SetThreadCount(1);
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline_parent";
            string tlogChildRootName = "ThreadProcTrackedAutoTLog";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogChildWriteFile = $"{tlogChildRootName}.write.2.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingChildThreadTrackedAuto");
            FileTracker.SuspendTracking();

            File.WriteAllText(sourceFile, "parent thread\r\n");

            Thread t = new Thread(ThreadProcAutoTLog);
            t.Start();
            t.Join(); // wait for our child to complete

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName); // parent will write an explicit tlog

            FileTracker.StopTrackingAndCleanup();
            Assert.False(File.Exists(tlogWriteFile));
            Assert.False(File.Exists(tlogChildRootName));
            string[] writtenlines = FileTrackerTestHelper.ReadLinesFromFile(sourceFile);
            Assert.Equal(2, writtenlines.Length);
            Assert.Equal("parent thread", writtenlines[0]);
            Assert.Equal("child thread", writtenlines[1]);

            File.Delete(tlogWriteFile);
            File.Delete(tlogChildWriteFile);
            File.Delete(sourceFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingChildThreadNotTrackedLocallyTracked()
        {
            FileTracker.SetThreadCount(1);
            string sourceFile = "inlinetrackingtest.txt";
            string tlogRootName = "foo_inline_parent";
            string tlogChildRootName = "ThreadProcLocallyTracked";
            string tlogWriteFile = $"{tlogRootName}.write.1.tlog";
            string tlogChildWriteFile = $"{tlogChildRootName}.write.2.tlog";

            File.Delete(tlogWriteFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), "InProcTrackingChildThreadNotTrackedLocallyTracked");
            FileTracker.SuspendTracking();

            File.WriteAllText(sourceFile, "parent thread\r\n");

            Thread t = new Thread(ThreadProcLocallyTracked);
            t.Start();
            t.Join(); // wait for our child to complete

            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName); // parent will write an explicit tlog

            FileTracker.StopTrackingAndCleanup();
            Assert.False(File.Exists(tlogWriteFile));
            string[] writtenlines = FileTrackerTestHelper.ReadLinesFromFile(sourceFile);
            string[] childLines = FileTrackerTestHelper.ReadLinesFromFile(tlogChildWriteFile);
            Assert.Equal(2, childLines.Length);
            Assert.Equal(2, writtenlines.Length);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), childLines[1]);
            Assert.Equal("parent thread", writtenlines[0]);
            Assert.Equal("child thread", writtenlines[1]);

            File.Delete(tlogWriteFile);
            File.Delete(tlogChildWriteFile);
            File.Delete(sourceFile);
        }

        private static void ThreadProcLocallyTracked()
        {
            FileTracker.StartTrackingContext(Path.GetFullPath("."), "ThreadProcLocallyTracked");
            string sourceFile = "inlinetrackingtest.txt";
            File.AppendAllText(sourceFile, "child thread\r\n");
            FileTracker.WriteContextTLogs(Path.GetFullPath("."), "ThreadProcLocallyTracked"); // will write an explicit tlog
            FileTracker.EndTrackingContext();
        }

        private static void ThreadProcAutoTLog()
        {
            string sourceFile = "inlinetrackingtest.txt";
            File.AppendAllText(sourceFile, "child thread\r\n");
        }

        private static void ThreadProcManualTLog()
        {
            string tlogRootName = "foo_inline_child";
            string sourceFile = "inlinetrackingtest.txt";
            File.AppendAllText(sourceFile, "child thread\r\n");
            FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
        }


        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void InProcTrackingChildCustomEnvironment()
        {
            string sourceFile = "allenvironment.txt";
            string commandFile = "inlinetrackingtest.cmd";
            string tlogRootName = "CustomEnvironment";
            string tlogReadFile = $"{tlogRootName}-cmd.read.1.tlog";
            string tlogWriteFile = $"{tlogRootName}-cmd.write.1.tlog";
            File.Delete(tlogWriteFile);

            File.WriteAllText(commandFile, "SET > " + sourceFile);

            FileTracker.StartTrackingContext(Path.GetFullPath("."), tlogRootName);

            ProcessStartInfo ps = new ProcessStartInfo("cmd.exe", "/C " + commandFile);

            ps.EnvironmentVariables.Add("TESTVAR", "THE_RIGHT_VALUE");
            ps.UseShellExecute = false;

            Process cmd = Process.Start(ps);
            cmd.WaitForExit();

            FileTracker.StopTrackingAndCleanup();

            // Read in the environment file and check that the variable that we set is there
            string[] envLines = File.ReadAllLines(sourceFile);
            int trackerEnvValueCount = 0;

            string varValue = null;
            string toolChainValue = null;
            foreach (string envLine in envLines)
            {
                if (envLine.StartsWith("TRACKER_", StringComparison.OrdinalIgnoreCase))
                {
                    trackerEnvValueCount++;
                }

                if (envLine.StartsWith("TESTVAR=", StringComparison.OrdinalIgnoreCase) && varValue == null)
                {
                    string[] varVal = envLine.Split(MSBuildConstants.EqualsChar);
                    varValue = varVal[1];
                }
                else if (envLine.StartsWith("TRACKER_TOOLCHAIN=", StringComparison.OrdinalIgnoreCase) && toolChainValue == null)
                {
                    string[] varVal = envLine.Split(MSBuildConstants.EqualsChar);
                    toolChainValue = varVal[1];
                }
            }

            Assert.True(trackerEnvValueCount >= 7); // "Not enough tracking environment set"
            Assert.Equal("THE_RIGHT_VALUE", varValue);
            Assert.Equal(tlogRootName + "-cmd", toolChainValue);
            string[] writeLines = FileTrackerTestHelper.ReadLinesFromFile(tlogWriteFile);
            string[] readLines = FileTrackerTestHelper.ReadLinesFromFile(tlogReadFile);
            Assert.Equal(2, writeLines.Length);
            Assert.Equal(2, readLines.Length);
            Assert.Equal(Path.GetFullPath(commandFile).ToUpperInvariant(), readLines[1]);
            Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(), writeLines[1]);

            File.Delete(tlogReadFile);
            File.Delete(tlogWriteFile);
            File.Delete(sourceFile);
            File.Delete(commandFile);
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void CreateFileDoesntRecordWriteIfNotWrittenTo()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "CreateFileDoesntRecordWriteIfNotWrittenTo");
            string readFile = Path.Combine(testDir, "readfile.txt");
            string tlogRootName = "CreateFileRead";

            if (Directory.Exists(testDir))
            {
                Directory.Delete(testDir, true /* recursive */);
            }

            try
            {
                Directory.CreateDirectory(testDir);
                File.WriteAllText(readFile, "this is some sample text that doesn't really matter");

                // wait a bit to give the timestamps time to settle
                Thread.Sleep(100);

                FileTracker.StartTrackingContext(testDir, tlogRootName);

                var buffer = new byte[10];
                using (FileStream fs = File.Open(readFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
                {
                    fs.ReadExactly(buffer, 0, 10);
                }

                FileTracker.WriteContextTLogs(testDir, tlogRootName);

                FileTrackerTestHelper.AssertFoundStringInTLog(readFile.ToUpperInvariant(), Path.Combine(testDir, tlogRootName + ".read.1.tlog"));
                FileTrackerTestHelper.AssertDidntFindStringInTLog(readFile.ToUpperInvariant(), Path.Combine(testDir, tlogRootName + ".write.1.tlog"));
            }
            finally
            {
                FileTracker.StopTrackingAndCleanup();
                if (Directory.Exists(testDir))
                {
                    Directory.Delete(testDir, true /* recursive */);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void CopyAlwaysRecordsWrites()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "CopyAlwaysRecordsWrites");
            string tlogRootName = "CopyFileTest";
            string copyFromFile = Path.Combine(testDir, "copyFrom.txt");
            string copyToFile = Path.Combine(testDir, "copyTo.txt");
            string tlogReadFile = Path.Combine(testDir, tlogRootName + ".read.1.tlog");
            string tlogWriteFile = Path.Combine(testDir, tlogRootName + ".write.1.tlog");

            if (Directory.Exists(testDir))
            {
                Directory.Delete(testDir, true /* recursive */);
            }

            try
            {
                Directory.CreateDirectory(testDir);

                try
                {
                    File.WriteAllText(copyFromFile, "text in the file!");

                    FileTracker.StartTrackingContext(testDir, tlogRootName);

                    File.Copy(copyFromFile, copyToFile);

                    FileTracker.WriteContextTLogs(testDir, tlogRootName);

                    FileTrackerTestHelper.AssertFoundStringInTLog(copyFromFile.ToUpperInvariant(), tlogReadFile);
                    FileTrackerTestHelper.AssertFoundStringInTLog(copyToFile.ToUpperInvariant(), tlogWriteFile);
                }
                finally
                {
                    File.Delete(tlogReadFile);
                    File.Delete(tlogWriteFile);
                    FileTracker.StopTrackingAndCleanup();
                }

                // wait a bit to give the timestamps time to settle
                Thread.Sleep(100);

                try
                {
                    File.Delete(copyToFile);

                    FileTracker.StartTrackingContext(testDir, tlogRootName);

                    File.Copy(copyFromFile, copyToFile);

                    FileTracker.WriteContextTLogs(testDir, tlogRootName);

                    FileTrackerTestHelper.AssertFoundStringInTLog(copyFromFile.ToUpperInvariant(), tlogReadFile);
                    FileTrackerTestHelper.AssertFoundStringInTLog(copyToFile.ToUpperInvariant(), tlogWriteFile);
                }
                finally
                {
                    FileTracker.StopTrackingAndCleanup();
                }
            }
            finally
            {
                if (Directory.Exists(testDir))
                {
                    Directory.Delete(testDir, true /* recursive */);
                }
            }
        }

        [Fact(Skip = "Needs investigation")]
        public void MoveAlwaysRecordsWrites()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "MoveAlwaysRecordsWrites");
            string tlogRootName = "MoveFileTest";
            string moveFromFile = Path.Combine(testDir, "MoveFrom.txt");
            string moveToFile = Path.Combine(testDir, "MoveTo.txt");
            string moveToFile2 = Path.Combine(testDir, "MoveTo2.txt");
            string tlogDeleteFile = Path.Combine(testDir, tlogRootName + ".delete.1.tlog");
            string tlogWriteFile = Path.Combine(testDir, tlogRootName + ".write.1.tlog");

            if (Directory.Exists(testDir))
            {
                Directory.Delete(testDir, true /* recursive */);
            }

            try
            {
                Directory.CreateDirectory(testDir);

                try
                {
                    File.WriteAllText(moveFromFile, "text in the file!");

                    FileTracker.StartTrackingContext(testDir, tlogRootName);

                    File.Move(moveFromFile, moveToFile);

                    FileTracker.WriteContextTLogs(testDir, tlogRootName);

                    FileTrackerTestHelper.AssertFoundStringInTLog(moveFromFile.ToUpperInvariant(), tlogDeleteFile);
                    FileTrackerTestHelper.AssertFoundStringInTLog(moveToFile.ToUpperInvariant(), tlogWriteFile);
                }
                finally
                {
                    File.Delete(tlogDeleteFile);
                    File.Delete(tlogWriteFile);
                    FileTracker.StopTrackingAndCleanup();
                }

                // wait a bit to give the timestamps time to settle
                Thread.Sleep(100);

                try
                {
                    File.WriteAllText(moveFromFile, "text in the file!");
                    File.Delete(moveToFile);

                    FileTracker.StartTrackingContext(testDir, tlogRootName);

                    File.Move(moveFromFile, moveToFile);
                    File.Move(moveToFile, moveToFile2);

                    FileTracker.WriteContextTLogs(testDir, tlogRootName);

                    FileTrackerTestHelper.AssertFoundStringInTLog(moveFromFile.ToUpperInvariant(), tlogDeleteFile);
                    FileTrackerTestHelper.AssertFoundStringInTLog(moveToFile.ToUpperInvariant(), tlogDeleteFile);
                    FileTrackerTestHelper.AssertFoundStringInTLog(moveToFile2.ToUpperInvariant(), tlogWriteFile);
                }
                finally
                {
                    FileTracker.StopTrackingAndCleanup();
                }
            }
            finally
            {
                if (Directory.Exists(testDir))
                {
                    Directory.Delete(testDir, true /* recursive */);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_SameCommand()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_SameCommand");
            FileUtilities.DeleteDirectoryNoThrow(testDir, true);

            try
            {
                Directory.CreateDirectory(testDir);

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "foo baz");

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i baz " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 3));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                FileUtilities.DeleteDirectoryNoThrow(testDir, true);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_DifferentCommands1()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_DifferentCommands1");
            FileUtilities.DeleteDirectoryNoThrow(testDir, true);

            try
            {
                Directory.CreateDirectory(testDir);
                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "foo baz");

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i foo " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "\"" + destinationFindstrPath + "\" /i baz " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 6));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                FileUtilities.DeleteDirectoryNoThrow(testDir, true);
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_DifferentCommands2()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_DifferentCommands2");

            try
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }

                Directory.CreateDirectory(testDir);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "");

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i baz " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "\"" + destinationFindstrPath + "\" /i foo " + tempFilePath, 2));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "\"" + destinationFindstrPath + "\" /i ba " + tempFilePath, 2));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 7));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_DifferentCommands3()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_DifferentCommands3");
            string oldCurrentDirectory = Directory.GetCurrentDirectory();
            try
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }

                Directory.CreateDirectory(testDir);

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "findstr.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "foo baz");

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(originalFindstrPath, "/i foo " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i baz " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "FIndsTr /i ba " + tempFilePath, 2));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-findstr*tlog", 8));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                Directory.SetCurrentDirectory(oldCurrentDirectory);

                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_DifferentCommands4()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_DifferentCommands4");
            string oldPath = Environment.GetEnvironmentVariable("PATH");

            try
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }

                Directory.CreateDirectory(testDir);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "");

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                Environment.SetEnvironmentVariable("PATH", Path.GetDirectoryName(destinationFindstrPath) + ";" + oldPath);

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/ip oo " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "abc.exe /i foo " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 6));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                Environment.SetEnvironmentVariable("PATH", oldPath);

                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleDifferentTools()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleDifferentTools");

            try
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }

                Directory.CreateDirectory(testDir);
                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "");

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i foo " + tempFilePath, 3));
                toolsToLaunch.Add(new Tuple<string, string, int>(null, "findstr /i foo " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 3));
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-findstr*tlog", 3));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }
            }
        }

        [Fact(Skip = "FileTracker tests require VS2015 Update 3 or a packaged version of Tracker.exe https://github.com/dotnet/msbuild/issues/649")]
        public void LaunchMultipleOfSameTool_DifferentContexts()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_DifferentContexts");
            FileUtilities.DeleteDirectoryNoThrow(testDir, true);
            try
            {
                Directory.CreateDirectory(testDir);

                string originalFindstrPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), "findstr.exe");
                string destinationFindstrPath = Path.Combine(testDir, "abc.exe");
                File.Copy(originalFindstrPath, destinationFindstrPath);

                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "foo baz");

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(destinationFindstrPath, "/i baz " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications =
                    new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest2", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-abc*tlog", 3));
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest2-abc*tlog", 3));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, false);
            }
            catch (Exception)
            {
                FileUtilities.DeleteDirectoryNoThrow(testDir, true);
            }
        }

#if ENABLE_TRACKER_TESTS // https://github.com/dotnet/msbuild/issues/12063
        [Fact(Skip = "Needs investigation")]
        public void LaunchMultipleOfSameTool_ToolLaunchesOthers()
        {
            string testDir = Path.Combine(Path.GetTempPath(), "LaunchMultipleOfSameTool_ToolLaunchesOthers");

            try
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }

                Directory.CreateDirectory(testDir);

                // File to run findstr against.
                string tempFilePath = Path.Combine(testDir, "bar.txt");
                File.WriteAllText(tempFilePath, "");

                // Sample app that runs findstr.
                string outputFile = Path.Combine(testDir, "FindstrLauncher.exe");
                string codeContent = @"
using System;
using System.Diagnostics;

namespace ConsoleApplication4
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.Length > 1)
            {
                for (int i = 0; i < Int32.Parse(args[0]); i++)
                {
                    Process p = Process.Start(""findstr"", ""/i foo "" + args[1]);
                    p.WaitForExit();
                }
            }
        }
    }
}";

                File.Delete(outputFile);

                string codeFile = Path.Combine(testDir, "Program.cs");
                File.WriteAllText(codeFile, codeContent);
                Csc csc = new Csc();
                csc.BuildEngine = new MockEngine3();
                csc.Sources = new ITaskItem[] { new TaskItem(codeFile) };
                csc.OutputAssembly = new TaskItem(outputFile);
                csc.Platform = "x86";
                bool compileSucceeded = csc.Execute();

                Assert.True(compileSucceeded);

                // Item1: appname
                // Item2: command line
                // Item3: number of times to launch
                IList<Tuple<string, string, int>> toolsToLaunch = new List<Tuple<string, string, int>>();
                toolsToLaunch.Add(new Tuple<string, string, int>(outputFile, outputFile + " 3 " + tempFilePath, 3));

                // Item1: FileTracker context name
                // Item2: Tuple <string, string, int> as described above
                IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications = new List<Tuple<string, IList<Tuple<string, string, int>>>>();
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest", toolsToLaunch));
                contextSpecifications.Add(new Tuple<string, IList<Tuple<string, string, int>>>("ProcessLaunchTest2", toolsToLaunch));

                // Item1: tlog pattern
                // Item2: # times it's expected to appear
                IList<Tuple<string, int>> tlogPatterns = new List<Tuple<string, int>>();
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-FindstrLauncher-findstr*tlog", 3));
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest-FindstrLauncher.*-findstr*tlog", 6));
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest2-FindstrLauncher-findstr*tlog", 3));
                tlogPatterns.Add(new Tuple<string, int>("ProcessLaunchTest2-FindstrLauncher.*-findstr*tlog", 6));

                LaunchDuplicateToolsAndVerifyTlogExistsForEach(testDir, contextSpecifications, tlogPatterns, createTestDirectory: false);
            }
            finally
            {
                if (FileUtilities.DirectoryExistsNoThrow(testDir))
                {
                    FileUtilities.DeleteDirectoryNoThrow(testDir, true);
                }
            }
        }
#endif // ENABLE_TRACKER_TESTS

        private static void InProcTrackingSpawnsToolWithTracker(bool useTrackerResponseFile)
        {
            const string testInFile = "test.in";
            const string testInFileContent = "foo";
            const string tool = "findstr";
            const string toolReadTlog = tool + ".read.1.tlog";
            const string rootingMarker = "jibbit goo";
            const string inprocTrackingContext = "Context1";
            const string tlogRootName = "foo_inline";
            const string sourceFile = "inlinetrackingtest.txt";
            const string trackerResponseFile = "test-tracker.rsp";
            const string fileTrackerParameters = "/d FileTracker.dll /r \"" + rootingMarker + "\"";

            File.Delete(toolReadTlog);
            File.Delete(sourceFile);
            FileTrackerTestHelper.WriteAll(testInFile, testInFileContent);
            FileTrackerTestHelper.WriteAll(trackerResponseFile, fileTrackerParameters);

            try
            {
                FileTracker.StartTrackingContext(Path.GetFullPath("."), inprocTrackingContext);
                File.WriteAllText(sourceFile, "this is a inline tracking test");

                string firstParameters = useTrackerResponseFile ? "@\"" + trackerResponseFile + "\"" : fileTrackerParameters;
                int exit = FileTrackerTestHelper.RunCommand(
                    s_defaultTrackerPath,
                    string.Format(
                        CultureInfo.CurrentCulture,
                        "{0} /c {1} /ip {2} {3}",
                        firstParameters,
                        tool,
                        testInFileContent,
                        testInFile));

                Assert.Equal(0, exit);
                Assert.Equal("^" + rootingMarker.ToUpperInvariant(),
                                       FileTrackerTestHelper.ReadLineFromFile(toolReadTlog, 1).ToUpperInvariant());
                FileTrackerTestHelper.AssertFoundStringInTLog(Path.GetFullPath(testInFile).ToUpperInvariant(), toolReadTlog);

                FileTracker.WriteContextTLogs(Path.GetFullPath("."), tlogRootName);
                Assert.Equal(Path.GetFullPath(sourceFile).ToUpperInvariant(),
                                       FileTrackerTestHelper.ReadLineFromFile(tlogRootName + ".write.1.tlog", 1).ToUpperInvariant());
            }
            finally
            {
                File.Delete(trackerResponseFile);
                File.Delete(testInFile);
                File.Delete(sourceFile);
                File.Delete(tlogRootName + ".write.1.tlog");
                File.Delete(tlogRootName + ".read.1.tlog");
                File.Delete(tool + ".read.1.tlog");
                File.Delete(inprocTrackingContext + "-Tracker.read.1.tlog");
                FileTracker.StopTrackingAndCleanup();
            }
        }

        private static void LaunchDuplicateToolsAndVerifyTlogExistsForEach(string tlogPath, IList<Tuple<string, IList<Tuple<string, string, int>>>> contextSpecifications, IList<Tuple<string, int>> tlogPatterns, bool createTestDirectory)
        {
            try
            {
                if (createTestDirectory)
                {
                    if (FileUtilities.DirectoryExistsNoThrow(tlogPath))
                    {
                        FileUtilities.DeleteDirectoryNoThrow(tlogPath, true);
                    }

                    Directory.CreateDirectory(tlogPath);
                }

                BackEndNativeMethods.STARTUP_INFO startInfo = new BackEndNativeMethods.STARTUP_INFO();
                startInfo.cb = Marshal.SizeOf<BackEndNativeMethods.STARTUP_INFO>();
                uint dwCreationFlags = BackEndNativeMethods.NORMALPRIORITYCLASS;

                startInfo.hStdError = BackEndNativeMethods.InvalidHandle;
                startInfo.hStdInput = BackEndNativeMethods.InvalidHandle;
                startInfo.hStdOutput = BackEndNativeMethods.InvalidHandle;
                startInfo.dwFlags = BackEndNativeMethods.STARTFUSESTDHANDLES;
                dwCreationFlags |= BackEndNativeMethods.CREATENOWINDOW;

                BackEndNativeMethods.SECURITY_ATTRIBUTES pSec = new BackEndNativeMethods.SECURITY_ATTRIBUTES();
                BackEndNativeMethods.SECURITY_ATTRIBUTES tSec = new BackEndNativeMethods.SECURITY_ATTRIBUTES();
                pSec.nLength = Marshal.SizeOf<BackEndNativeMethods.SECURITY_ATTRIBUTES>();
                tSec.nLength = Marshal.SizeOf<BackEndNativeMethods.SECURITY_ATTRIBUTES>();

                BackEndNativeMethods.PROCESS_INFORMATION pInfo = new BackEndNativeMethods.PROCESS_INFORMATION();

                foreach (var specification in contextSpecifications)
                {
                    // Item1: FileTracker context name
                    // Item2: Tuple <string, string, int> as described below
                    FileTracker.StartTrackingContext(tlogPath, specification.Item1);

                    foreach (var processSpecification in specification.Item2)
                    {
                        // Item1: appname
                        // Item2: command line
                        // Item3: number of times to launch
                        for (int i = 0; i < processSpecification.Item3; i++)
                        {
                            BackEndNativeMethods.CreateProcess(processSpecification.Item1, processSpecification.Item2,
                                                        ref pSec, ref tSec,
                                                        false, dwCreationFlags,
                                                        BackEndNativeMethods.NullPtr, null, ref startInfo, out pInfo);
                        }
                    }

                    FileTracker.WriteContextTLogs(tlogPath, specification.Item1);
                    FileTracker.StopTrackingAndCleanup();
                }

                int tlogCount = 0;
                foreach (Tuple<string, int> pattern in tlogPatterns)
                {
                    tlogCount += pattern.Item2;
                }

                // make sure the disk write gets time for NTFS to recognize its existence.  Estimate time needed to sleep based
                // roughly on the number of tlogs that we're looking for (presumably roughly proportional to the number of tlogs
                // being written.
                Thread.Sleep(Math.Max(200, 250 * tlogCount));

                // Item1: The pattern the tlog name should follow
                // Item2: The number of tlogs following that pattern that should exist in the output directory
                foreach (Tuple<string, int> pattern in tlogPatterns)
                {
                    string[] tlogNames = Directory.GetFiles(tlogPath, pattern.Item1, SearchOption.TopDirectoryOnly);

                    Assert.Equal(pattern.Item2, tlogNames.Length);
                }
            }
            finally
            {
                if (FileUtilities.DirectoryExistsNoThrow(tlogPath))
                {
                    FileUtilities.DeleteDirectoryNoThrow(tlogPath, true);
                }
            }
        }
    }

    internal static class FileTrackerTestHelper
    {
        public static int RunCommand(string command, string arguments)
            => RunCommandWithOptions(command, arguments, true /* print stdout & stderr */, out string _);

        public static int RunCommandNoStdOut(string command, string arguments)
            => RunCommandWithOptions(command, arguments, false /* don't print stdout & stderr */, out string _);

        public static int RunCommandWithLog(string command, string arguments, out string outputAsLog)
            => RunCommandWithOptions(command, arguments, true /* print stdout & stderr */, out outputAsLog);

        private static int RunCommandWithOptions(string command, string arguments, bool printOutput, out string outputAsLog)
        {
            outputAsLog = null;
            ProcessStartInfo si = new ProcessStartInfo(command, arguments);
            if (printOutput)
            {
                si.RedirectStandardOutput = true;
                si.RedirectStandardError = true;
            }

            si.UseShellExecute = false;
            si.CreateNoWindow = true;
            Process p = Process.Start(si);
            p.WaitForExit();

            if (printOutput)
            {
                outputAsLog = "StdOut: \n" + p.StandardOutput.ReadToEnd() + "\nStdErr: \n" + p.StandardError.ReadToEnd();
                Console.Write(outputAsLog);
            }

            return p.ExitCode;
        }

        public static string ReadLineFromFile(string filename, int linenumber) => File.ReadAllLines(filename)[linenumber];

        public static string[] ReadLinesFromFile(string filename) => File.ReadAllLines(filename);

        public static void CleanTlogs()
        {
            string[] tlogFiles = Directory.GetFiles(".", "*.tlog", SearchOption.AllDirectories);

            foreach (string file in tlogFiles)
            {
                File.Delete(file);
            }

            File.Delete("test.in");
            File.Delete("t\u1EBCst.in");
            File.Delete("test.out");

            if (Directory.Exists("outdir"))
            {
                Directory.Delete("outdir", true);
            }
        }

        public static void WriteAll(string filename, string content) => File.WriteAllText(filename, content);


        public static bool FindStringInTlog(string file, string tlog)
            => ReadLinesFromFile(tlog).Contains(file, StringComparer.OrdinalIgnoreCase);

        public static void AssertDidntFindStringInTLog(string file, string tlog)
        {
            string[] lines = ReadLinesFromFile(tlog);

            for (int i = 0; i < lines.Length; i++)
            {
                if (file.Equals(lines[i], StringComparison.OrdinalIgnoreCase))
                {
                    Assert.Fail("Found string '" + file + "' in '" + tlog + "' at line " + i + ", when it shouldn't have been in the log at all.");
                }
            }
        }

        public static void AssertFoundStringInTLog(string file, string tlog, int timesFound)
        {
            int timesFoundSoFar = 0;
            string[] lines = ReadLinesFromFile(tlog);

            foreach (string line in lines)
            {
                if (file.Equals(line, StringComparison.OrdinalIgnoreCase))
                {
                    timesFoundSoFar++;

                    if (timesFoundSoFar == timesFound)
                    {
                        break;
                    }
                }
            }

            if (timesFound != timesFoundSoFar)
            {
                Assert.Fail("Searched " + tlog + " but didn't find " + timesFound + " instances of " + file);
            }
        }

        public static void AssertFoundStringInTLog(string file, string tlog) => AssertFoundStringInTLog(file, tlog, 1);
    }
}
#endif
