Port everything to the new task system

The history of the task system changes is at
https://github.com/shana/task-system (8b3880cd69)

Also get rid of the separate NPath provider for cleaner code
This commit is contained in:
Andreia Gaita 2017-05-30 15:40:18 +02:00
Родитель fa6a7e2a8f
Коммит 782d623653
122 изменённых файлов: 4456 добавлений и 969 удалений

Просмотреть файл

@ -21,9 +21,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Net.Http-net_3_5", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopyLibrariesToDevelopmentFolder", "src\packaging\CopyLibrariesToDevelopmentFolder\CopyLibrariesToDevelopmentFolder.csproj", "{44257C81-EE4A-4817-9AF4-A26C02AA6DD4}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "src\UnitTests\UnitTests.csproj", "{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests", "src\tests\UnitTests\UnitTests.csproj", "{69F13D9D-AD56-4EEC-AE10-D528EE23E1A9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "src\IntegrationTests\IntegrationTests.csproj", "{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "src\tests\IntegrationTests\IntegrationTests.csproj", "{1AC3F82E-AEAE-4C84-825C-207BB264FCFA}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}"
EndProject
@ -31,7 +31,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "packaging", "packaging", "{
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CopyLibrariesToPackageProject", "src\packaging\CopyLibrariesToPackageProject\CopyLibrariesToPackageProject.csproj", "{7DEF4226-7740-457F-9199-34174C49A978}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "src\TestUtils\TestUtils.csproj", "{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtils", "src\tests\TestUtils\TestUtils.csproj", "{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TaskSystem", "src\tests\TaskSystemIntegrationTests\TaskSystem.csproj", "{1A382F40-FD9E-43E1-89C1-320073F35CE9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "src\tests\TestApp\TestApp.csproj", "{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -106,6 +110,18 @@ Global
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.dev|Any CPU.Build.0 = Debug|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF}.Release|Any CPU.Build.0 = Release|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.dev|Any CPU.Build.0 = Debug|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A382F40-FD9E-43E1-89C1-320073F35CE9}.Release|Any CPU.Build.0 = Release|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.dev|Any CPU.ActiveCfg = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.dev|Any CPU.Build.0 = Debug|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -119,5 +135,7 @@ Global
{1AC3F82E-AEAE-4C84-825C-207BB264FCFA} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
{7DEF4226-7740-457F-9199-34174C49A978} = {B50B646C-3B86-4BDA-9F2B-766F96608CE0}
{66A1D219-F61D-4AE4-9BD7-AAEB97276FFF} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
{1A382F40-FD9E-43E1-89C1-320073F35CE9} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
{08B87D2A-8CF1-4211-B7AA-5209F00F72F8} = {D17F1B4C-42DC-4E78-BCEF-9F239A084C4D}
EndGlobalSection
EndGlobal

Просмотреть файл

@ -18,6 +18,7 @@ using System.Runtime.InteropServices;
[assembly: InternalsVisibleTo("TestUtils", AllInternalsVisible = true)]
[assembly: InternalsVisibleTo("UnitTests", AllInternalsVisible = true)]
[assembly: InternalsVisibleTo("IntegrationTests", AllInternalsVisible = true)]
[assembly: InternalsVisibleTo("TaskSystemIntegrationTests", AllInternalsVisible = true)]
//Required for NSubstitute
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2", AllInternalsVisible = true)]

Просмотреть файл

@ -40,9 +40,9 @@ namespace GitHub.Unity
GitClient = new GitClient(Environment, ProcessManager, Platform.CredentialManager, TaskManager);
}
public virtual Task Run()
public virtual ITask Run()
{
Task task = null;
ITask task = null;
try
{
task = RunInternal();
@ -64,8 +64,7 @@ namespace GitHub.Unity
{
if (FileSystem == null)
{
FileSystem = new FileSystem();
NPathFileSystemProvider.Current = FileSystem;
FileSystem = NPath.FileSystem;
}
FileSystem.SetCurrentDirectory(Environment.UnityProjectPath);
@ -105,45 +104,45 @@ namespace GitHub.Unity
}
}
private async Task RunInternal()
private ITask RunInternal()
{
await ThreadingHelper.SwitchToThreadAsync();
var gitSetup = new GitSetup(Environment, FileSystem, CancellationToken);
var expectedPath = gitSetup.GitInstallationPath;
var setupDone = await gitSetup.SetupIfNeeded(
//new Progress<float>(x => logger.Trace("Percentage: {0}", x)),
//new Progress<long>(x => logger.Trace("Remaining: {0}", x))
);
if (setupDone)
Environment.GitExecutablePath = gitSetup.GitExecutablePath;
else
Environment.GitExecutablePath = await LookForGitInstallationPath();
logger.Trace("Environment.GitExecutablePath \"{0}\" Exists:{1}", gitSetup.GitExecutablePath, gitSetup.GitExecutablePath.FileExists());
await RestartRepository();
if (Environment.IsWindows)
return new FuncTask<ProgressReport>(TaskManager.Token, _ => new ProgressReport())
.Then(async (s, progress) =>
{
string credentialHelper = null;
var gitConfigGetTask = new GitConfigGetTask("credential.helper", GitConfigSource.Global, CancellationToken);
var setupDone = await gitSetup.SetupIfNeeded(progress.Percentage, progress.Remaining);
if (setupDone)
Environment.GitExecutablePath = gitSetup.GitExecutablePath;
else
Environment.GitExecutablePath = await LookForGitInstallationPath();
logger.Trace("Environment.GitExecutablePath \"{0}\" Exists:{1}", gitSetup.GitExecutablePath, gitSetup.GitExecutablePath.FileExists());
await RestartRepository();
await gitConfigGetTask.Task;
if (string.IsNullOrEmpty(credentialHelper))
if (Environment.IsWindows)
{
var gitConfigSetTask = new GitConfigSetTask("credential.helper", "wincred", GitConfigSource.Global, CancellationToken);
string credentialHelper = null;
var gitConfigGetTask = new GitConfigGetTask("credential.helper", GitConfigSource.Global, CancellationToken);
await gitConfigSetTask.Task;
await gitConfigGetTask.Task;
if (string.IsNullOrEmpty(credentialHelper))
{
var gitConfigSetTask = new GitConfigSetTask("credential.helper", "wincred", GitConfigSource.Global, CancellationToken);
await gitConfigSetTask.Task;
}
}
}
});
}
private async Task<string> LookForGitInstallationPath()
private async Task<NPath> LookForGitInstallationPath()
{
NPath cachedGitInstallPath = null;
var path = SystemSettings.Get("GitInstallPath");
@ -154,11 +153,11 @@ namespace GitHub.Unity
if (cachedGitInstallPath == null ||
!cachedGitInstallPath.DirectoryExists())
{
return await GitEnvironment.FindGitInstallationPath(ProcessManager);
return await GitEnvironment.FindGitInstallationPath(ProcessManager).StartAwait();
}
else
{
return cachedGitInstallPath.ToString();
return cachedGitInstallPath;
}
}

Просмотреть файл

@ -22,17 +22,17 @@ namespace GitHub.Unity
return Environment.GetEnvironmentVariable(variable);
}
public string UnityApplication { get; set; }
public string UnityAssetsPath { get; set; }
public string UnityProjectPath { get; set; }
public string ExtensionInstallPath { get; set; }
public NPath UnityApplication { get; set; }
public NPath UnityAssetsPath { get; set; }
public NPath UnityProjectPath { get; set; }
public NPath ExtensionInstallPath { get; set; }
public NPath UserCachePath { get; set; }
public NPath SystemCachePath { get; set; }
public string Path { get { return Environment.GetEnvironmentVariable("PATH"); } }
public NPath Path { get { return Environment.GetEnvironmentVariable("PATH").ToNPath(); } }
public string NewLine { get { return Environment.NewLine; } }
private string gitExecutablePath;
public string GitExecutablePath
private NPath gitExecutablePath;
public NPath GitExecutablePath
{
get { return gitExecutablePath; }
set
@ -43,8 +43,8 @@ namespace GitHub.Unity
}
}
private string gitInstallPath;
public string GitInstallPath
private NPath gitInstallPath;
public NPath GitInstallPath
{
get
{
@ -55,11 +55,11 @@ namespace GitHub.Unity
{
if (IsWindows)
{
gitInstallPath = GitExecutablePath.ToNPath().Parent.Parent;
gitInstallPath = GitExecutablePath.Parent.Parent;
}
else
{
gitInstallPath = GitExecutablePath.ToNPath().Parent;
gitInstallPath = GitExecutablePath.Parent;
}
logger.Trace("Setting GitInstallPath to " + gitInstallPath);
}
@ -70,7 +70,7 @@ namespace GitHub.Unity
}
}
public string RepositoryPath { get { return Repository.LocalPath; } }
public NPath RepositoryPath { get { return Repository.LocalPath; } }
public IRepository Repository { get; set; }
public bool IsWindows { get { return OnWindows; } }

Просмотреть файл

@ -29,7 +29,7 @@ namespace GitHub.Unity
private readonly RepositoryPathConfiguration paths;
private readonly CancellationToken cancellationToken;
private readonly NPath[] ignoredPaths;
private readonly AutoResetEvent autoResetEvent;
private readonly ManualResetEventSlim pauseEvent;
private readonly bool disableNative;
private NativeInterface nativeInterface;
private bool running;
@ -52,11 +52,11 @@ namespace GitHub.Unity
this.cancellationToken = cancellationToken;
ignoredPaths = new[] {
platform.Environment.UnityProjectPath.ToNPath().Combine("Library"),
platform.Environment.UnityProjectPath.ToNPath().Combine("Temp")
platform.Environment.UnityProjectPath.Combine("Library"),
platform.Environment.UnityProjectPath.Combine("Temp")
};
autoResetEvent = new AutoResetEvent(false);
pauseEvent = new ManualResetEventSlim();
disableNative = !platform.Environment.IsWindows;
}
@ -84,6 +84,7 @@ namespace GitHub.Unity
}
running = true;
pauseEvent.Reset();
task = Task.Factory.StartNew(WatcherLoop, cancellationToken, TaskCreationOptions.None, ThreadingHelper.TaskScheduler);
}
@ -98,7 +99,7 @@ namespace GitHub.Unity
Logger.Trace("Stopping watcher");
running = false;
autoResetEvent.Set();
pauseEvent.Set();
}
private void WatcherLoop()
@ -164,11 +165,11 @@ namespace GitHub.Unity
if (repositoryChanged)
{
Logger.Debug("RepositoryChanged");
Logger.Trace("RepositoryChanged");
RepositoryChanged?.Invoke();
}
if (autoResetEvent.WaitOne(200))
if (pauseEvent.Wait(200))
{
break;
}
@ -270,7 +271,7 @@ namespace GitHub.Unity
var branch = string.Join(@"/", relativePathElements.ToArray());
Logger.Debug("LocalBranchChanged: {0}", branch);
Logger.Trace("LocalBranchChanged: {0}", branch);
LocalBranchChanged?.Invoke(branch);
}
else if (fileEvent.Type == EventType.DELETED)
@ -290,7 +291,7 @@ namespace GitHub.Unity
var branch = string.Join(@"/", relativePathElements.ToArray());
Logger.Debug("LocalBranchDeleted: {0}", branch);
Logger.Trace("LocalBranchDeleted: {0}", branch);
LocalBranchDeleted?.Invoke(branch);
}
else if (fileEvent.Type == EventType.RENAMED)
@ -314,7 +315,7 @@ namespace GitHub.Unity
var branch = string.Join(@"/", relativePathElements.ToArray());
Logger.Debug("LocalBranchCreated: {0}", branch);
Logger.Trace("LocalBranchCreated: {0}", branch);
LocalBranchCreated?.Invoke(branch);
}
}

Просмотреть файл

@ -4,13 +4,6 @@ using System.Threading.Tasks;
namespace GitHub.Unity
{
static class AsyncExtensions
{
public static void Forget(this Task task)
{
}
}
static class TaskExt
{
public static async Task<TEventArgs> FromEvent<TEventHandler, TEventArgs>(

Просмотреть файл

@ -151,6 +151,14 @@ namespace GitHub.Unity
}
}
public static class ListExtensions
{
public static string Join<T>(this IEnumerable<T> list, string separator)
{
return String.Join(separator, list.Cast<string>().ToArray());
}
}
public struct StringResult
{
public string Chunk;

Просмотреть файл

@ -86,136 +86,136 @@ namespace GitHub.Unity
public ITask<GitStatus?> Status(IOutputProcessor<GitStatus?> processor = null, ITask dependsOn = null)
{
return new GitStatusTask(new GitObjectFactory(environment), cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<List<GitLogEntry>> Log(BaseOutputListProcessor<GitLogEntry> processor = null, ITask dependsOn = null)
{
return new GitLogTask(new GitObjectFactory(environment), cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> GetConfig(string key, GitConfigSource configSource, IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitConfigGetTask(key, configSource, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> SetConfig(string key, string value, GitConfigSource configSource, IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitConfigSetTask(key, value, configSource, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<List<GitLock>> ListLocks(bool local, BaseOutputListProcessor<GitLock> processor = null, ITask dependsOn = null)
{
return new GitListLocksTask(new GitObjectFactory(environment), local, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Pull(string remote, string branch, IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitPullTask(remote, branch, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Push(string remote, string branch,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitPushTask(remote, branch, true, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Fetch(string remote,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitFetchTask(remote, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> SwitchBranch(string branch,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitSwitchBranchesTask(branch, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> DeleteBranch(string branch, bool deleteUnmerged = false,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitBranchDeleteTask(branch, deleteUnmerged, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> CreateBranch(string branch, string baseBranch,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitBranchCreateTask(branch, baseBranch, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> RemoteAdd(string remote, string url,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitRemoteAddTask(remote, url, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> RemoteRemove(string remote,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitRemoteRemoveTask(remote, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> RemoteChange(string remote, string url,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitRemoteChangeTask(remote, url, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Commit(string message, string body,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitCommitTask(message, body, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Add(List<string> files,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitAddTask(files, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Remove(List<string> files,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitRemoveFromIndexTask(files, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> AddAndCommit(List<string> files, string message, string body,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return Add(files)
.ContinueWith(new GitCommitTask(body, message, cancellationToken)
.ConfigureGitProcess(processManager));
.Then(new GitCommitTask(body, message, cancellationToken)
.Configure(processManager));
}
public ITask<string> Lock(string file,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitLockTask(file, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
public ITask<string> Unlock(string file, bool force,
IOutputProcessor<string> processor = null, ITask dependsOn = null)
{
return new GitUnlockTask(file, force, cancellationToken, processor, dependsOn)
.ConfigureGitProcess(processManager);
.Configure(processManager);
}
protected static ILogging Logger { get; } = Logging.GetLogger<GitClient>();

Просмотреть файл

@ -126,6 +126,7 @@
<Compile Include="NewTaskSystem\TaskCanceledExceptions.cs" />
<Compile Include="NewTaskSystem\TaskExtensions.cs" />
<Compile Include="NewTaskSystem\TaskManager.cs" />
<Compile Include="ProgressReport.cs" />
<Compile Include="Tasks\Simple\GitLfsInstallTask.cs" />
<Compile Include="Extensions\GitStatusExtensions.cs" />
<Compile Include="Tasks\Simple\GitRemoteChangeTask.cs" />
@ -151,7 +152,6 @@
<Compile Include="Process\GitRemoteFunction.cs" />
<Compile Include="Process\IProcessManager.cs" />
<Compile Include="IO\NiceIO.cs" />
<Compile Include="IO\NPathFileSystemProvider.cs" />
<Compile Include="Process\RemoteListOutputProcessor.cs" />
<Compile Include="IRepository.cs" />
<Compile Include="ISettings.cs" />
@ -176,7 +176,6 @@
<Compile Include="SimpleJson.cs" />
<Compile Include="Tasks\CachedTask.cs" />
<Compile Include="Git\FailureSeverity.cs" />
<Compile Include="Tasks\Simple\FindGitTask.cs" />
<Compile Include="Tasks\Simple\GitAddTask.cs" />
<Compile Include="Git\GitBranch.cs" />
<Compile Include="Tasks\Simple\GitBranchCreateTask.cs" />

Просмотреть файл

@ -1,13 +1,25 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
namespace GitHub.Unity
{
public static class Guard
class PropertyOrFieldNullException : Exception
{
public PropertyOrFieldNullException(string message) : base(message)
{ }
}
static class Guard
{
public static void PropertyOrFieldNotNull(object value, string name)
{
if (value != null) return;
string message = String.Format(CultureInfo.InvariantCulture, $"Property or Field {name} is null");
throw new PropertyOrFieldNullException(message);
}
public static void ArgumentNotNull(object value, string name)
{
if (value != null) return;
@ -76,4 +88,4 @@ namespace GitHub.Unity
public static bool InUnitTestRunner { get; set; }
}
}
}

Просмотреть файл

@ -8,19 +8,19 @@ namespace GitHub.Unity
string GetEnvironmentVariable(string v);
string GetSpecialFolder(Environment.SpecialFolder folder);
string Path { get; }
NPath Path { get; }
string NewLine { get; }
string GitExecutablePath { get; set; }
NPath GitExecutablePath { get; set; }
bool IsWindows { get; }
bool IsLinux { get; }
bool IsMac { get; }
string UnityApplication { get; set; }
string UnityAssetsPath { get; set; }
string UnityProjectPath { get; set; }
string ExtensionInstallPath { get; set; }
NPath UnityApplication { get; set; }
NPath UnityAssetsPath { get; set; }
NPath UnityProjectPath { get; set; }
NPath ExtensionInstallPath { get; set; }
NPath UserCachePath { get; set; }
string RepositoryPath { get; }
string GitInstallPath { get; }
NPath RepositoryPath { get; }
NPath GitInstallPath { get; }
IRepository Repository { get; set; }
NPath SystemCachePath { get; set; }
}

Просмотреть файл

@ -7,7 +7,7 @@ namespace GitHub.Unity
{
public static string FindCommonPath(IEnumerable<string> paths)
{
var pathsArray = paths.Select(s => s.ToNPath().Parent).ToArray();
var pathsArray = paths.Where(s => s != null).Select(s => s.ToNPath().Parent).ToArray();
var maxDepth = pathsArray.Max(path => path.Depth);
var deepestPath = pathsArray.First(path => path.Depth == maxDepth);

Просмотреть файл

@ -1,9 +0,0 @@
using GitHub.Unity;
namespace GitHub.Unity
{
class NPathFileSystemProvider
{
public static IFileSystem Current { get; set; }
}
}

Просмотреть файл

@ -27,7 +27,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
@ -38,7 +37,10 @@ NiceIO
GitHub.Unity
#endif
{
public class NPath : IEquatable<NPath>, IComparable
#if NICEIO
public
#endif
class NPath : IEquatable<NPath>, IComparable
{
private static StringComparison? pathStringComparison;
private static StringComparison PathStringComparison
@ -46,7 +48,7 @@ GitHub.Unity
get
{
if (!pathStringComparison.HasValue)
pathStringComparison = IsLinux() ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
pathStringComparison = IsLinux ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
return pathStringComparison.Value;
}
}
@ -219,7 +221,7 @@ GitHub.Unity
ThrowIfRoot();
var newElements = (string[])_elements.Clone();
newElements[newElements.Length - 1] = NPathFileSystemProvider.Current.ChangeExtension(_elements[_elements.Length - 1], WithDot(extension));
newElements[newElements.Length - 1] = FileSystem.ChangeExtension(_elements[_elements.Length - 1], WithDot(extension));
if (extension == string.Empty)
newElements[newElements.Length - 1] = newElements[newElements.Length - 1].TrimEnd('.');
return new NPath(newElements, _isRelative, _driveLetter);
@ -245,7 +247,7 @@ GitHub.Unity
public string FileNameWithoutExtension
{
get { return NPathFileSystemProvider.Current.GetFileNameWithoutExtension(FileName); }
get { return FileSystem.GetFileNameWithoutExtension(FileName); }
}
public IEnumerable<string> Elements
@ -275,7 +277,7 @@ GitHub.Unity
public bool DirectoryExists(NPath append)
{
return NPathFileSystemProvider.Current.DirectoryExists(Combine(append).ToString());
return FileSystem.DirectoryExists(Combine(append).ToString());
}
public bool FileExists(string append = "")
@ -285,7 +287,7 @@ GitHub.Unity
public bool FileExists(NPath append)
{
return NPathFileSystemProvider.Current.FileExists(Combine(append).ToString());
return FileSystem.FileExists(Combine(append).ToString());
}
public string ExtensionWithDot
@ -346,6 +348,13 @@ GitHub.Unity
return sb.ToString();
}
public static implicit operator string(NPath path)
{
if (path == null)
return null;
return path.ToString();
}
static char Slash(SlashMode slashMode)
{
switch (slashMode)
@ -355,9 +364,7 @@ GitHub.Unity
case SlashMode.Forward:
return '/';
default:
if (NPathFileSystemProvider.Current != null)
return NPathFileSystemProvider.Current.DirectorySeparatorChar;
return Path.DirectorySeparatorChar;
return FileSystem.DirectorySeparatorChar;
}
}
@ -365,9 +372,7 @@ GitHub.Unity
{
get
{
if (NPathFileSystemProvider.Current != null)
return NPathFileSystemProvider.Current.DirectorySeparatorChar;
return Path.DirectorySeparatorChar;
return FileSystem.DirectorySeparatorChar;
}
}
@ -424,9 +429,9 @@ GitHub.Unity
// Suitable nullity checks etc, of course :)
hash = hash * 23 + _isRelative.GetHashCode();
foreach (var element in _elements)
hash = hash * 23 + element.GetHashCode();
hash = hash * 23 + (IsLinux ? element : element.ToUpperInvariant()).GetHashCode();
if (_driveLetter != null)
hash = hash * 23 + _driveLetter.GetHashCode();
hash = hash * 23 + (IsLinux ? _driveLetter : _driveLetter.ToUpperInvariant()).GetHashCode();
return hash;
}
}
@ -471,7 +476,7 @@ GitHub.Unity
public IEnumerable<NPath> Files(string filter, bool recurse = false)
{
return NPathFileSystemProvider.Current.GetFiles(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s));
return FileSystem.GetFiles(ToString(), filter, recurse ? System.IO.SearchOption.AllDirectories : System.IO.SearchOption.TopDirectoryOnly).Select(s => new NPath(s));
}
public IEnumerable<NPath> Files(bool recurse = false)
@ -491,7 +496,7 @@ GitHub.Unity
public IEnumerable<NPath> Directories(string filter, bool recurse = false)
{
return NPathFileSystemProvider.Current.GetDirectories(ToString(), filter, recurse ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly).Select(s => new NPath(s));
return FileSystem.GetDirectories(ToString(), filter, recurse ? System.IO.SearchOption.AllDirectories : System.IO.SearchOption.TopDirectoryOnly).Select(s => new NPath(s));
}
public IEnumerable<NPath> Directories(bool recurse = false)
@ -507,7 +512,7 @@ GitHub.Unity
ThrowIfRelative();
ThrowIfRoot();
EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.WriteAllBytes(ToString(), new byte[0]);
FileSystem.WriteAllBytes(ToString(), new byte[0]);
return this;
}
@ -530,7 +535,7 @@ GitHub.Unity
if (IsRoot)
throw new NotSupportedException("CreateDirectory is not supported on a root level directory because it would be dangerous:" + ToString());
NPathFileSystemProvider.Current.CreateDirectory(ToString());
FileSystem.CreateDirectory(ToString());
return this;
}
@ -594,7 +599,7 @@ GitHub.Unity
absoluteDestination.EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.FileCopy(ToString(), absoluteDestination.ToString(), true);
FileSystem.FileCopy(ToString(), absoluteDestination.ToString(), true);
return absoluteDestination;
}
@ -617,13 +622,13 @@ GitHub.Unity
throw new NotSupportedException("Delete is not supported on a root level directory because it would be dangerous:" + ToString());
if (FileExists())
NPathFileSystemProvider.Current.FileDelete(ToString());
FileSystem.FileDelete(ToString());
else if (DirectoryExists())
try
{
NPathFileSystemProvider.Current.DirectoryDelete(ToString(), true);
FileSystem.DirectoryDelete(ToString(), true);
}
catch (IOException)
catch (System.IO.IOException)
{
if (deleteMode == DeleteMode.Normal)
throw;
@ -657,7 +662,7 @@ GitHub.Unity
Files().Delete();
Directories().Delete();
}
catch (IOException)
catch (System.IO.IOException)
{
if (Files(true).Any())
throw;
@ -674,7 +679,7 @@ GitHub.Unity
var random = new Random();
while (true)
{
var candidate = new NPath(NPathFileSystemProvider.Current.GetTempPath() + "/" + myprefix + "_" + random.Next());
var candidate = new NPath(FileSystem.GetTempPath() + "/" + myprefix + "_" + random.Next());
if (!candidate.Exists())
return candidate.CreateDirectory();
}
@ -701,13 +706,13 @@ GitHub.Unity
if (FileExists())
{
dest.EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.FileMove(ToString(), dest.ToString());
FileSystem.FileMove(ToString(), dest.ToString());
return dest;
}
if (DirectoryExists())
{
NPathFileSystemProvider.Current.DirectoryMove(ToString(), dest.ToString());
FileSystem.DirectoryMove(ToString(), dest.ToString());
return dest;
}
@ -722,7 +727,7 @@ GitHub.Unity
{
get
{
return new NPath(NPathFileSystemProvider.Current.GetCurrentDirectory());
return new NPath(FileSystem.GetCurrentDirectory());
}
}
@ -730,7 +735,7 @@ GitHub.Unity
{
get
{
if (NPathFileSystemProvider.Current.DirectorySeparatorChar == '\\')
if (FileSystem.DirectorySeparatorChar == '\\')
return new NPath(Environment.GetEnvironmentVariable("USERPROFILE"));
return new NPath(Environment.GetEnvironmentVariable("HOME"));
}
@ -740,7 +745,7 @@ GitHub.Unity
{
get
{
return new NPath(NPathFileSystemProvider.Current.GetTempPath());
return new NPath(FileSystem.GetTempPath());
}
}
@ -783,7 +788,7 @@ GitHub.Unity
public NPath FileMustExist()
{
if (!FileExists())
throw new FileNotFoundException("File was expected to exist : " + ToString());
throw new System.IO.FileNotFoundException("File was expected to exist : " + ToString());
return this;
}
@ -791,7 +796,7 @@ GitHub.Unity
public NPath DirectoryMustExist()
{
if (!DirectoryExists())
throw new DirectoryNotFoundException("Expected directory to exist : " + ToString());
throw new System.IO.DirectoryNotFoundException("Expected directory to exist : " + ToString());
return this;
}
@ -855,35 +860,35 @@ GitHub.Unity
{
ThrowIfRelative();
EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.WriteAllText(ToString(), contents);
FileSystem.WriteAllText(ToString(), contents);
return this;
}
public string ReadAllText()
{
ThrowIfRelative();
return NPathFileSystemProvider.Current.ReadAllText(ToString());
return FileSystem.ReadAllText(ToString());
}
public NPath WriteAllLines(string[] contents)
{
ThrowIfRelative();
EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.WriteAllLines(ToString(), contents);
FileSystem.WriteAllLines(ToString(), contents);
return this;
}
public string[] ReadAllLines()
{
ThrowIfRelative();
return NPathFileSystemProvider.Current.ReadAllLines(ToString());
return FileSystem.ReadAllLines(ToString());
}
public NPath WriteAllBytes(byte[] contents)
{
ThrowIfRelative();
EnsureParentDirectoryExists();
NPathFileSystemProvider.Current.WriteAllBytes(ToString(), contents);
FileSystem.WriteAllBytes(ToString(), contents);
return this;
}
@ -907,25 +912,37 @@ GitHub.Unity
return true;
}
private static bool IsLinux()
private static bool? isLinux;
private static bool IsLinux
{
return NPathFileSystemProvider.Current.DirectoryExists("/proc");
get
{
if (!isLinux.HasValue)
isLinux = FileSystem.DirectoryExists("/proc");
return isLinux.Value;
}
}
public static implicit operator NPath(string value)
private static IFileSystem _fileSystem;
public static IFileSystem FileSystem
{
if (value == null) return null;
return new NPath(value);
}
public static implicit operator string(NPath path)
{
return path?.ToString();
get
{
if (_fileSystem == null)
_fileSystem = new FileSystem();
return _fileSystem;
}
set
{
_fileSystem = value;
}
}
}
public static class Extensions
#if NICEIO
public
#endif
static class Extensions
{
public static IEnumerable<NPath> Copy(this IEnumerable<NPath> self, string dest)
{
@ -967,6 +984,8 @@ GitHub.Unity
public static NPath ToNPath(this string path)
{
if (path == null)
return null;
return new NPath(path);
}
}
@ -983,4 +1002,4 @@ GitHub.Unity
Normal,
Soft
}
}
}

Просмотреть файл

@ -34,7 +34,7 @@ namespace GitHub.Unity
/// <summary>
/// Gets the local path of the repository.
/// </summary>
string LocalPath { get; }
NPath LocalPath { get; }
bool IsGitHub { get; }
/// <summary>
/// Gets the current remote of the repository.

Просмотреть файл

@ -174,7 +174,7 @@ namespace GitHub.Unity
{
logger.Warning("Archive \"{0}\" missing", archiveFilePath.ToString());
archiveFilePath = environment.ExtensionInstallPath.ToNPath().Combine(archiveFilePath);
archiveFilePath = environment.ExtensionInstallPath.Combine(archiveFilePath);
if (!archiveFilePath.FileExists())
{
logger.Warning("Archive \"{0}\" missing, returning", archiveFilePath.ToString());
@ -236,7 +236,7 @@ namespace GitHub.Unity
{
logger.Warning("Archive \"{0}\" missing", archiveFilePath.ToString());
archiveFilePath = environment.ExtensionInstallPath.ToNPath().Combine(archiveFilePath);
archiveFilePath = environment.ExtensionInstallPath.Combine(archiveFilePath);
if (!archiveFilePath.FileExists())
{
logger.Warning("Archive \"{0}\" missing, returning", archiveFilePath.ToString());

Просмотреть файл

@ -170,7 +170,7 @@ namespace GitHub.Unity
public LocalSettings(IEnvironment environment)
{
SettingsPath = environment.UnityProjectPath.ToNPath().Combine(RelativeSettingsPath);
SettingsPath = environment.UnityProjectPath.Combine(RelativeSettingsPath);
}
protected override string SettingsFileName { get { return settingsFileName; } }

Просмотреть файл

@ -8,101 +8,172 @@ namespace GitHub.Unity
class ActionTask : TaskBase
{
protected Action<bool> Callback { get; }
protected Action<bool, Exception> CallbackWithException { get; }
public ActionTask(CancellationToken token, Action<bool> action)
: base(token)
public ActionTask(CancellationToken token, Action<bool> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
Name = "ActionTask";
}
public ActionTask(CancellationToken token, Action<bool> action, ITask dependsOn)
: base(token, dependsOn)
public ActionTask(CancellationToken token, Action<bool, Exception> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
this.CallbackWithException = action;
Name = "ActionTask<Exception>";
}
public ActionTask(Task task)
: base(task)
{
Name = "ActionTask(Task)";
}
protected override void Run(bool success)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
base.Run(success);
RaiseOnStart();
Callback?.Invoke(success);
Exception exception = null;
try
{
Callback?.Invoke(success);
if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
CallbackWithException?.Invoke(success, thrown);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (!success)
throw DependsOn.Task.Exception.InnerException;
if (exception != null)
throw exception;
}
}
class ActionTask<T> : TaskBase
{
protected Action<bool, T> Callback { get; }
protected Action<bool, Exception, T> CallbackWithException { get; }
public ActionTask(CancellationToken token, Action<bool, T> action, ITask<T> dependsOn)
: base(token, dependsOn)
public ActionTask(CancellationToken token, Action<bool, T> action, ITask<T> dependsOn, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
Task = new Task(() => Run(DependsOn.Successful, DependsOn.Successful ? ((ITask<T>)DependsOn).Result : default(T)),
Token, TaskCreationOptions.None);
Name = $"ActionTask<{typeof(T)}>";
}
protected override void Run(bool success)
public ActionTask(CancellationToken token, Action<bool, Exception, T> action, ITask<T> dependsOn, bool always = false)
: base(token, dependsOn, always)
{
throw new NotImplementedException();
Guard.ArgumentNotNull(action, "action");
this.CallbackWithException = action;
Task = new Task(() => Run(DependsOn.Successful, DependsOn.Successful ? ((ITask<T>)DependsOn).Result : default(T)),
Token, TaskCreationOptions.None);
Name = $"ActionTask<Exception, {typeof(T)}>";
}
public ActionTask(Task task)
: base(task)
{
Name = $"ActionTask<{typeof(T)}>(Task)";
}
protected virtual void Run(bool success, T previousResult)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
base.Run(success);
RaiseOnStart();
Callback?.Invoke(success, previousResult);
Exception exception = null;
try
{
Callback?.Invoke(success, previousResult);
if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
CallbackWithException?.Invoke(success, thrown, previousResult);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (!success)
throw DependsOn.Task.Exception.InnerException;
if (exception != null)
throw exception;
}
}
class FuncTask<T> : TaskBase<T>
{
protected Func<bool, T> Callback { get; }
protected Func<bool, Exception, T> CallbackWithException { get; }
public FuncTask(CancellationToken token, Func<bool, T> action)
: base(token)
public FuncTask(CancellationToken token, Func<bool, T> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
Name = $"FuncTask<{typeof(T)}>";
}
public FuncTask(CancellationToken token, Func<bool, T> action, ITask dependsOn)
: base(token, dependsOn)
public FuncTask(CancellationToken token, Func<bool, Exception, T> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
this.CallbackWithException = action;
Name = $"FuncTask<Exception, {typeof(T)}>";
}
public FuncTask(Task<T> task)
: base(task)
{
Name = $"FuncTask<{typeof(T)}>(Task)";
}
protected override T RunWithReturn(bool success)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
T result = base.RunWithReturn(success);
RaiseOnStart();
T result = default(T);
result = Callback(success);
Exception exception = null;
try
{
if (Callback != null)
{
result = Callback(success);
}
else if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
result = CallbackWithException(success, thrown);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (!success)
throw DependsOn.Task.Exception.InnerException;
if (exception != null)
throw exception;
return result;
}
@ -111,88 +182,174 @@ namespace GitHub.Unity
class FuncTask<T, TResult> : TaskBase<T, TResult>
{
protected Func<bool, T, TResult> Callback { get; }
protected Func<bool, Exception, T, TResult> CallbackWithException { get; }
public FuncTask(CancellationToken token, Func<bool, T, TResult> action, ITask<T> dependsOn)
: base(token, dependsOn)
public FuncTask(CancellationToken token, Func<bool, T, TResult> action, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
Name = $"FuncTask<{typeof(T)}, {typeof(TResult)}>";
}
public FuncTask(CancellationToken token, Func<bool, Exception, T, TResult> action, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.CallbackWithException = action;
Name = $"FuncTask<{typeof(T)}, Exception, {typeof(TResult)}>";
}
public FuncTask(Task<TResult> task)
: base(task)
{
Name = $"FuncTask<{typeof(T)}, {typeof(TResult)}>(Task)";
}
protected override TResult RunWithData(bool success, T previousResult)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
var result = base.RunWithData(success, previousResult);
RaiseOnStart();
var result = Callback(success, previousResult);
Exception exception = null;
try
{
if (Callback != null)
{
result = Callback(success, previousResult);
}
else if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
result = CallbackWithException(success, thrown, previousResult);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (!success)
throw DependsOn.Task.Exception.InnerException;
if (exception != null)
throw exception;
return result;
}
}
class FuncListTask<T> : ListTaskBase<List<T>, T>
class FuncListTask<T> : DataTaskBase<T, List<T>>
{
protected Func<bool, List<T>> Callback { get; }
protected Func<bool, Exception, List<T>> CallbackWithException { get; }
public FuncListTask(CancellationToken token, Func<bool, List<T>> action)
: base(token)
public FuncListTask(CancellationToken token, Func<bool, List<T>> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
}
public FuncListTask(CancellationToken token, Func<bool, List<T>> action, ITask dependsOn)
: base(token, dependsOn)
public FuncListTask(CancellationToken token, Func<bool, Exception, List<T>> action, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
this.CallbackWithException = action;
}
public FuncListTask(Task<List<T>> task)
: base(task)
{ }
protected override List<T> RunWithReturn(bool success)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
var result = base.RunWithReturn(success);
RaiseOnStart();
List<T> result = null;
result = Callback(success);
Exception exception = null;
try
{
if (Callback != null)
{
result = Callback(success);
}
else if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
result = CallbackWithException(success, thrown);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (exception != null)
throw exception;
if (result == null)
result = new List<T>();
if (!success)
throw DependsOn.Task.Exception.InnerException;
return result;
}
}
class FuncListTask<TDependentResult, TResult, TData> : ListTaskBase<TDependentResult, TResult, TData>
class FuncListTask<T, TData, TResult> : DataTaskBase<T, TData, List<TResult>>
{
protected Func<bool, TDependentResult, TResult> Callback { get; }
protected Func<bool, T, List<TResult>> Callback { get; }
protected Func<bool, Exception, T, List<TResult>> CallbackWithException { get; }
public FuncListTask(CancellationToken token, Func<bool, TDependentResult, TResult> action, ITask<TDependentResult> dependsOn)
: base(token, dependsOn)
public FuncListTask(CancellationToken token, Func<bool, T, List<TResult>> action, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(action, "action");
this.Callback = action;
}
protected override TResult RunWithData(bool success, TDependentResult previousResult)
public FuncListTask(CancellationToken token, Func<bool, Exception, T, List<TResult>> action, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
Guard.ArgumentNotNull(action, "action");
this.CallbackWithException = action;
}
public FuncListTask(Task<List<TResult>> task)
: base(task)
{ }
protected override List<TResult> RunWithData(bool success, T previousResult)
{
var result = base.RunWithData(success, previousResult);
RaiseOnStart();
TResult result = default(TResult);
result = Callback(success, previousResult);
Exception exception = null;
try
{
if (Callback != null)
{
result = Callback(success, previousResult);
}
else if (CallbackWithException != null)
{
Exception thrown = GetThrownException();
thrown = thrown != null ? thrown.InnerException : thrown;
result = CallbackWithException(success, thrown, previousResult);
}
}
catch (Exception ex)
{
Errors = ex.Message;
exception = ex;
}
RaiseOnEnd();
if (!success)
throw DependsOn.Task.Exception.InnerException;
if (exception != null)
throw exception;
return result;
}

Просмотреть файл

@ -15,7 +15,7 @@ namespace GitHub.Unity
event Action<T> OnEntry;
}
interface IOutputProcessor<T, TData> : IOutputProcessor<T>
interface IOutputProcessor<TData, T> : IOutputProcessor<T>
{
new event Action<TData> OnEntry;
}
@ -36,7 +36,7 @@ namespace GitHub.Unity
protected ILogging Logger { get { return logger = logger ?? Logging.GetLogger(GetType()); } }
}
abstract class BaseOutputProcessor<T, TData> : BaseOutputProcessor<T>, IOutputProcessor<T, TData>
abstract class BaseOutputProcessor<TData, T> : BaseOutputProcessor<T>, IOutputProcessor<TData, T>
{
public new event Action<TData> OnEntry;
@ -46,7 +46,7 @@ namespace GitHub.Unity
}
}
abstract class BaseOutputListProcessor<T> : BaseOutputProcessor<List<T>, T>
abstract class BaseOutputListProcessor<T> : BaseOutputProcessor<T, List<T>>
{
protected override void RaiseOnEntry(T entry)
{
@ -114,4 +114,16 @@ namespace GitHub.Unity
return true;
}
}
class FirstLineIsPathOutputProcessor : FirstResultOutputProcessor<NPath>
{
protected override bool ProcessLine(string line, out NPath result)
{
result = null;
if (String.IsNullOrEmpty(line))
return false;
result = line.ToNPath();
return true;
}
}
}

Просмотреть файл

@ -15,8 +15,8 @@ namespace GitHub.Unity
static TaskSchedulerExcludingThread()
{
executeEntryMethod = typeof(Task).GetMethod("ExecuteEntry", BindingFlags.NonPublic | BindingFlags.Instance);
}
public TaskSchedulerExcludingThread(int threadToExclude)
{
ThreadToExclude = threadToExclude;
@ -29,7 +29,6 @@ namespace GitHub.Unity
private static bool ExecuteEntry(Task task, bool flag)
{
return (bool)executeEntryMethod.Invoke(task, new object[] { flag });
}
@ -125,9 +124,10 @@ namespace GitHub.Unity
exclusiveTaskScheduler = new ConcurrentExclusiveTaskScheduler(this, new Queue<Task>(), 1);
}
public void Wait()
public async Task Wait()
{
taskExecuting?.Wait();
if (taskExecuting != null)
await taskExecuting;
}
/// <summary>Notifies the interleave that new work has arrived to be processed.</summary>
@ -139,7 +139,7 @@ namespace GitHub.Unity
if (token.IsCancellationRequested) return;
// Otherwise, run the processor. Store the task and then start it to ensure that
// Otherwise, run the processor. Store the task and then start it to ensure that
// the assignment happens before the body of the task runs.
taskExecuting = new Task(ConcurrentExclusiveInterleaveProcessor, CancellationToken.None, TaskCreationOptions.None);
taskExecuting.Start(parallelOptions.TaskScheduler);
@ -168,8 +168,8 @@ namespace GitHub.Unity
exclusiveTaskScheduler.ExecuteTask(task);
// Just because we executed the task doesn't mean it's "complete",
// if it has child tasks that have not yet completed
// and will complete later asynchronously. To account for this,
// if a task isn't yet completed, leave the interleave processor
// and will complete later asynchronously. To account for this,
// if a task isn't yet completed, leave the interleave processor
// but leave it still in a running state. When the task completes,
// we'll come back in and keep going. Note that the children
// must not be scheduled to this interleave, or this will deadlock.

Просмотреть файл

@ -16,5 +16,6 @@ namespace GitHub.Unity
T ScheduleExclusive<T>(T task) where T : ITask;
T ScheduleUI<T>(T task) where T : ITask;
void Stop();
Task Wait();
}
}

Просмотреть файл

@ -11,13 +11,13 @@ namespace GitHub.Unity
{
static class ProcessTaskExtensions
{
public static T ConfigureGitProcess<T>(this T task, IProcessManager processManager, bool withInput = false)
public static T Configure<T>(this T task, IProcessManager processManager, bool withInput = false)
where T : IProcess
{
return processManager.ConfigureGitProcess(task, withInput);
return processManager.Configure(task, withInput);
}
public static T Configure<T>(this T task, IProcessManager processManager, string executable, string arguments, string workingDirectory, bool withInput)
public static T Configure<T>(this T task, IProcessManager processManager, string executable, string arguments, string workingDirectory = null, bool withInput = false)
where T : IProcess
{
return processManager.Configure(task, executable, arguments, workingDirectory, withInput);
@ -43,9 +43,9 @@ namespace GitHub.Unity
void Configure(ProcessStartInfo psi, IOutputProcessor<T> processor);
}
interface IProcessTask<T, TData> : ITask<T, TData>, IProcess
interface IProcessTask<TData, T> : ITask<TData, T>, IProcess
{
void Configure(ProcessStartInfo psi, IOutputProcessor<T, TData> processor);
void Configure(ProcessStartInfo psi, IOutputProcessor<TData, T> processor);
}
class ProcessWrapper
@ -81,7 +81,7 @@ namespace GitHub.Unity
{
Process.OutputDataReceived += (s, e) =>
{
//logger.Trace("OutputData \"" + (e.Data == null ? "'null'" : e.Data) + "\" exited:" + process.HasExited);
Logger.Trace("OutputData \"" + (e.Data == null ? "'null'" : e.Data) + "\"");
string encodedData = null;
if (e.Data != null)
@ -98,7 +98,7 @@ namespace GitHub.Unity
{
if (e.Data != null)
{
//logger.Trace("ErrorData \"" + (e.Data == null ? "'null'" : e.Data) + "\" exited:" + process.HasExited);
Logger.Trace("ErrorData \"" + (e.Data == null ? "'null'" : e.Data) + "\"");
}
string encodedData = null;
@ -165,7 +165,7 @@ namespace GitHub.Unity
private bool WaitForExit(int milliseconds)
{
//logger.Debug("WaitForExit - time: {0}ms", milliseconds);
//Logger.Debug("WaitForExit - time: {0}ms", milliseconds);
// Workaround for a bug in which some data may still be processed AFTER this method returns true, thus losing the data.
// http://connect.microsoft.com/VisualStudio/feedback/details/272125/waitforexit-and-waitforexit-int32-provide-different-and-undocumented-implementations
@ -192,26 +192,14 @@ namespace GitHub.Unity
public event Action<IProcess> OnStartProcess;
public event Action<IProcess> OnEndProcess;
private string errors = null;
private Exception thrownException = null;
public ProcessTask(CancellationToken token)
: base(token)
{
}
public ProcessTask(CancellationToken token, ITask dependsOn)
: base(token, dependsOn)
{
}
public ProcessTask(CancellationToken token, IOutputProcessor<T> outputProcessor)
: base(token)
{
this.outputProcessor = outputProcessor;
}
public ProcessTask(CancellationToken token, IOutputProcessor<T> outputProcessor, ITask dependsOn)
public ProcessTask(CancellationToken token, IOutputProcessor<T> outputProcessor = null, ITask dependsOn = null)
: base(token, dependsOn)
{
this.outputProcessor = outputProcessor;
@ -226,7 +214,7 @@ namespace GitHub.Unity
public ProcessTask(CancellationToken token, string arguments, IOutputProcessor<T> outputProcessor = null, ITask dependsOn = null)
: base(token, dependsOn)
{
Guard.ArgumentNotNull(token, "token");
Guard.ArgumentNotNull(token, nameof(token));
this.outputProcessor = outputProcessor;
ProcessArguments = arguments;
@ -237,6 +225,8 @@ namespace GitHub.Unity
Guard.ArgumentNotNull(psi, "psi");
ConfigureOutputProcessor();
Guard.PropertyOrFieldNotNull(outputProcessor, nameof(outputProcessor));
Process = new Process { StartInfo = psi, EnableRaisingEvents = true };
ProcessName = psi.FileName;
}
@ -245,6 +235,9 @@ namespace GitHub.Unity
{
outputProcessor = processor ?? outputProcessor;
ConfigureOutputProcessor();
Guard.PropertyOrFieldNotNull(outputProcessor, nameof(outputProcessor));
Process = new Process { StartInfo = psi, EnableRaisingEvents = true };
ProcessName = psi.FileName;
}
@ -254,6 +247,9 @@ namespace GitHub.Unity
Guard.ArgumentNotNull(existingProcess, "existingProcess");
ConfigureOutputProcessor();
Guard.PropertyOrFieldNotNull(outputProcessor, nameof(outputProcessor));
Process = existingProcess;
ProcessName = existingProcess.StartInfo.FileName;
}
@ -281,21 +277,16 @@ namespace GitHub.Unity
protected override T RunWithReturn(bool success)
{
if (!success)
{
throw DependsOn.Task.Exception.InnerException;
}
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
var result = base.RunWithReturn(success);
wrapper = new ProcessWrapper(Process, outputProcessor,
RaiseOnStart,
() =>
{
RaiseOnEnd();
if (errors != null)
if (Errors != null)
{
OnErrorData?.Invoke(errors);
OnErrorData?.Invoke(Errors);
if (thrownException == null)
throw new ProcessException(this);
else
@ -305,17 +296,19 @@ namespace GitHub.Unity
(ex, error) =>
{
thrownException = ex;
errors = error;
Errors = error;
},
Token);
wrapper.Run();
if (outputProcessor != null)
return outputProcessor.Result;
if (typeof(T) == typeof(string))
return (T)(object)(Process.StartInfo.CreateNoWindow ? "Process finished" : "Process running");
return default(T);
result = outputProcessor.Result;
if (result == null && typeof(T) == typeof(string))
result = (T)(object)(Process.StartInfo.CreateNoWindow ? "Process finished" : "Process running");
return result;
}
public Process Process { get; set; }
@ -326,10 +319,9 @@ namespace GitHub.Unity
public virtual string ProcessArguments { get; }
}
class ProcessTaskWithListOutput<T> : ListTaskBase<List<T>, T>, ITask<List<T>, T>, IProcessTask<List<T>, T>
class ProcessTaskWithListOutput<T> : DataTaskBase<T, List<T>>, IProcessTask<T, List<T>>
{
private IOutputProcessor<List<T>, T> outputProcessor;
private string errors = null;
private IOutputProcessor<T, List<T>> outputProcessor;
private Exception thrownException = null;
private ProcessWrapper wrapper;
@ -337,23 +329,12 @@ namespace GitHub.Unity
public event Action<IProcess> OnStartProcess;
public event Action<IProcess> OnEndProcess;
public ProcessTaskWithListOutput(CancellationToken token)
: base(token)
{
}
public ProcessTaskWithListOutput(CancellationToken token, IOutputProcessor<List<T>, T> outputProcessor)
: this(token)
{
this.outputProcessor = outputProcessor;
}
public ProcessTaskWithListOutput(CancellationToken token, ITask dependsOn)
: base(token, dependsOn)
{
}
public ProcessTaskWithListOutput(CancellationToken token, IOutputProcessor<List<T>, T> outputProcessor, ITask dependsOn)
public ProcessTaskWithListOutput(CancellationToken token, IOutputProcessor<T, List<T>> outputProcessor = null, ITask dependsOn = null)
: base(token, dependsOn)
{
this.outputProcessor = outputProcessor;
@ -364,6 +345,9 @@ namespace GitHub.Unity
Guard.ArgumentNotNull(psi, "psi");
ConfigureOutputProcessor();
Guard.PropertyOrFieldNotNull(outputProcessor, nameof(outputProcessor));
Process = new Process { StartInfo = psi, EnableRaisingEvents = true };
ProcessName = psi.FileName;
}
@ -373,11 +357,12 @@ namespace GitHub.Unity
Guard.ArgumentNotNull(existingProcess, "existingProcess");
ConfigureOutputProcessor();
Guard.PropertyOrFieldNotNull(outputProcessor, nameof(outputProcessor));
Process = existingProcess;
ProcessName = existingProcess.StartInfo.FileName;
}
public virtual void Configure(ProcessStartInfo psi, IOutputProcessor<List<T>, T> processor)
public virtual void Configure(ProcessStartInfo psi, IOutputProcessor<T, List<T>> processor)
{
Guard.ArgumentNotNull(psi, "psi");
Guard.ArgumentNotNull(processor, "processor");
@ -411,21 +396,16 @@ namespace GitHub.Unity
protected override List<T> RunWithReturn(bool success)
{
if (!success)
{
throw DependsOn.Task.Exception.InnerException;
}
Logger.Debug(String.Format("Executing id:{0} success?:{1}", Task.Id, success));
var result = base.RunWithReturn(success);
wrapper = new ProcessWrapper(Process, outputProcessor,
RaiseOnStart,
() =>
{
RaiseOnEnd();
if (errors != null)
if (Errors != null)
{
OnErrorData?.Invoke(errors);
OnErrorData?.Invoke(Errors);
if (thrownException == null)
throw new ProcessException(this);
else
@ -435,14 +415,16 @@ namespace GitHub.Unity
(ex, error) =>
{
thrownException = ex;
errors = error;
Errors = error;
},
Token);
wrapper.Run();
if (outputProcessor != null)
return outputProcessor.Result;
return new List<T>();
result = outputProcessor.Result;
if (result == null)
result = new List<T>();
return result;
}
public Process Process { get; set; }
@ -452,4 +434,20 @@ namespace GitHub.Unity
public virtual string ProcessName { get; protected set; }
public virtual string ProcessArguments { get; }
}
class SimpleProcessTask : ProcessTask<string>
{
private readonly NPath fullPathToExecutable;
private readonly string arguments;
public SimpleProcessTask(NPath fullPathToExecutable, string arguments, CancellationToken token)
: base(token, new FirstNonNullLineOutputProcessor())
{
this.fullPathToExecutable = fullPathToExecutable;
this.arguments = arguments;
}
public override string ProcessName => fullPathToExecutable.FileName;
public override string ProcessArguments => arguments;
}
}

Просмотреть файл

@ -6,31 +6,30 @@ namespace GitHub.Unity
{
interface ITask : IAsyncResult
{
T ContinueWith<T>(T continuation, bool always = true) where T : ITask;
// Continues and also sends a flag indicating whether the current task was successful or not
ITask ContinueWith(Action<bool> continuation, bool always = true);
// Continues and also sends a flag indicating whether the current task was successful or not
ITask ContinueWithUI(Action<bool> continuation, bool always = true);
T Then<T>(T continuation, bool always = false) where T : ITask;
ITask Finally(Action<bool, Exception> continuation, TaskAffinity affinity = TaskAffinity.Concurrent);
ITask SetDependsOn(ITask dependsOn);
ITask Start();
ITask Start(TaskScheduler scheduler);
void Wait();
bool Wait(int milliseconds);
bool Successful { get; }
string Errors { get; }
Task Task { get; }
string Name { get; }
TaskAffinity Affinity { get; }
TaskAffinity Affinity { get; set; }
CancellationToken Token { get; }
TaskBase DependsOn { get; }
event Action<ITask> OnStart;
event Action<ITask> OnEnd;
ITask SetDependsOn(ITask dependsOn);
}
interface ITask<TResult> : ITask
{
ActionTask<TResult> ContinueWith(Action<bool, TResult> continuation, bool always = true);
FuncTask<TResult, T> ContinueWith<T>(Func<bool, TResult, T> continuation, bool always = true);
ActionTask<TResult> ContinueWithUI(Action<bool, TResult> continuation, bool always = true);
FuncTask<TResult, T> ContinueWithUI<T>(Func<bool, TResult, T> continuation, bool always = true);
ITask<TResult> Finally(Func<bool, Exception, TResult, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent);
ITask Finally(Action<bool, Exception, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent);
new ITask<TResult> Start();
new ITask<TResult> Start(TaskScheduler scheduler);
TResult Result { get; }
new Task<TResult> Task { get; }
@ -38,7 +37,7 @@ namespace GitHub.Unity
new event Action<ITask<TResult>> OnEnd;
}
interface ITask<T, TData> : ITask<T>
interface ITask<TData, T> : ITask<T>
{
event Action<TData> OnData;
}
@ -46,24 +45,26 @@ namespace GitHub.Unity
abstract class TaskBase : ITask
{
protected const TaskContinuationOptions runAlwaysOptions = TaskContinuationOptions.None;
protected const TaskContinuationOptions runOnSuccessOptions = TaskContinuationOptions.NotOnCanceled | TaskContinuationOptions.NotOnFaulted;
protected const TaskContinuationOptions runOnSuccessOptions = TaskContinuationOptions.OnlyOnRanToCompletion;
protected const TaskContinuationOptions runOnFaultOptions = TaskContinuationOptions.OnlyOnFaulted;
public event Action<ITask> OnStart;
public event Action<ITask> OnEnd;
public TaskBase(CancellationToken token)
: this(token, null)
{
}
protected bool previousSuccess = true;
protected Exception previousException;
protected object previousResult;
public TaskBase(CancellationToken token, ITask dependsOn)
protected TaskBase continuation;
protected bool continuationAlways;
public TaskBase(CancellationToken token, ITask dependsOn = null, bool always = false)
{
Guard.ArgumentNotNull(token, "token");
Token = token;
DependsOn = dependsOn;
Task = new Task(() => Run(DependsOn?.Successful ?? true), Token, TaskCreationOptions.None);
Task = new Task(() => Run(DependsOn?.Successful ?? previousSuccess), Token, TaskCreationOptions.None);
dependsOn?.Then(this, always);
}
public TaskBase(Task task)
@ -71,55 +72,124 @@ namespace GitHub.Unity
Task = task;
}
protected TaskBase()
{ }
public ITask SetDependsOn(ITask dependsOn)
{
DependsOn = dependsOn;
DependsOn = (TaskBase)dependsOn;
return this;
}
public T ContinueWith<T>(T continuation, bool always = true)
public virtual T Then<T>(T continuation, bool always = false)
where T : ITask
{
Guard.ArgumentNotNull(continuation, "continuation");
continuation.SetDependsOn(this);
Task.ContinueWith(_ => continuation.Start(), Token,
always ? runAlwaysOptions : runOnSuccessOptions,
TaskManager.GetScheduler(continuation.Affinity));
this.continuation = (TaskBase)(object)continuation;
this.continuationAlways = always;
return continuation;
}
public ITask ContinueWith(Action<bool> continuation, bool always = true)
public ITask Finally(Action<bool, Exception> continuation, TaskAffinity affinity = TaskAffinity.Concurrent)
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new ActionTask(Token, continuation, this);
return ContinueWith(ret, always);
var ret = new ActionTask(Token, continuation, this, true) { Affinity = affinity, Name = "Finally" };
DependsOn?.SetFaultHandler(ret);
ret.ContinuationIsFinally = true;
return ret;
}
public ITask ContinueWithUI(Action<bool> continuation, bool always = true)
internal virtual ITask Finally<T>(T continuation)
where T : ITask
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new ActionTask(Token, continuation, this) { Affinity = TaskAffinity.UI };
return ContinueWith(ret, always);
continuation.SetDependsOn(this);
this.continuation = (TaskBase)(object)continuation;
this.continuationAlways = true;
DependsOn?.SetFaultHandler((TaskBase)(object)continuation);
return continuation;
}
internal void SetFaultHandler(TaskBase handler)
{
Task.ContinueWith(t => handler.Start(t), Token,
TaskContinuationOptions.OnlyOnFaulted,
TaskManager.GetScheduler(handler.Affinity));
DependsOn?.SetFaultHandler(handler);
}
public virtual ITask Start()
{
TaskManager.Instance.Schedule(this);
//Task.Start(TaskManager.GetScheduler(Affinity));
return this;
var depends = GetFirstDepends();
if (depends != null)
{
depends.Run();
return this;
}
else
{
return TaskManager.Instance.Schedule(this);
}
}
protected void Run()
{
if (Task.Status == TaskStatus.Created)
{
TaskManager.Instance.Schedule(this);
}
else
{
RunContinuation();
}
}
protected void Start(Task task)
{
previousSuccess = task.Status == TaskStatus.RanToCompletion && task.Status != TaskStatus.Faulted;
previousException = task.Exception;
Task.Start(TaskManager.GetScheduler(Affinity));
}
public virtual ITask Start(TaskScheduler scheduler)
{
if (DependsOn != null && DependsOn.Task.Status == TaskStatus.Created)
DependsOn.Start();
else
if (Task.Status == TaskStatus.Created)
{
Logger.Trace(String.Format($"Starting {Affinity} {Task.Id} {Name}"));
Task.Start(scheduler);
}
RunContinuation();
return this;
}
protected virtual void RunContinuation()
{
if (continuation != null)
{
Task.ContinueWith(_ => ((TaskBase)(object)continuation).Run(), Token, continuationAlways ? runAlwaysOptions : runOnSuccessOptions,
TaskManager.GetScheduler(continuation.Affinity));
}
}
protected TaskBase GetFirstDepends()
{
var depends = DependsOn;
if (depends == null)
return null;
return depends.GetFirstDepends(null);
}
protected TaskBase GetFirstDepends(TaskBase ret)
{
ret = (Task.Status == TaskStatus.Created ? this : ret);
var depends = DependsOn;
if (depends == null)
return ret;
return depends.GetFirstDepends(ret);
}
public virtual void Wait()
{
Task.Wait(Token);
@ -131,7 +201,9 @@ namespace GitHub.Unity
}
protected virtual void Run(bool success)
{ }
{
Logger.Trace(String.Format("Executing {0}({1}) success?:{2}", Name, Task.Id, success));
}
protected virtual void RaiseOnStart()
{
@ -143,76 +215,143 @@ namespace GitHub.Unity
OnEnd?.Invoke(this);
}
protected AggregateException GetThrownException()
{
if (DependsOn == null)
return null;
if (DependsOn.Task.Status != TaskStatus.Created && !DependsOn.Successful)
{
return DependsOn.Task.Exception;
}
return DependsOn.GetThrownException();
}
public virtual bool Successful { get { return Task.Status == TaskStatus.RanToCompletion && Task.Status != TaskStatus.Faulted; } }
public string Errors { get; protected set; }
public Task Task { get; protected set; }
public bool IsCompleted { get { return (Task as IAsyncResult).IsCompleted; } }
public WaitHandle AsyncWaitHandle { get { return (Task as IAsyncResult).AsyncWaitHandle; } }
public object AsyncState { get { return (Task as IAsyncResult).AsyncState; } }
public bool CompletedSynchronously { get { return (Task as IAsyncResult).CompletedSynchronously; } }
public virtual string Name { get; set; }
public virtual TaskAffinity Affinity { get; set; }
private ILogging logger;
protected ILogging Logger { get { return logger = logger ?? Logging.GetLogger(GetType()); } }
protected ITask DependsOn { get; private set; }
protected CancellationToken Token { get; }
public TaskBase DependsOn { get; private set; }
public CancellationToken Token { get; }
internal TaskBase Continuation => continuation;
internal bool ContinuationAlways => continuationAlways;
internal bool ContinuationIsFinally { get; set; }
}
abstract class TaskBase<TResult> : TaskBase, ITask<TResult>
{
public TaskBase(CancellationToken token, ITask dependsOn = null)
: base(token, dependsOn)
private DeferredContinuation deferred;
protected class DeferredContinuation
{
Task = new Task<TResult>(() => RunWithReturn(DependsOn?.Successful ?? true), Token, TaskCreationOptions.None);
public bool Always;
public Func<TResult, ITask> GetContinueWith;
}
public ActionTask<TResult> ContinueWith(Action<bool, TResult> continuation, bool always = true)
protected DeferredContinuation GetDeferred()
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new ActionTask<TResult>(Token, continuation, this);
return ContinueWith(ret, always);
return deferred;
}
public FuncTask<TResult, T> ContinueWith<T>(Func<bool, TResult, T> continuation, bool always = true)
public TaskBase(CancellationToken token, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new FuncTask<TResult, T>(Token, continuation, this);
return ContinueWith(ret, always);
Task = new Task<TResult>(() =>
{
var ret = RunWithReturn(DependsOn?.Successful ?? previousSuccess);
AdjustNextTask(ret);
return ret;
}, Token, TaskCreationOptions.None);
}
public ActionTask<TResult> ContinueWithUI(Action<bool, TResult> continuation, bool always = true)
protected void AdjustNextTask(TResult ret)
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new ActionTask<TResult>(Token, continuation, this) { Affinity = TaskAffinity.UI };
return ContinueWith(ret, always);
var def = GetDeferred();
if (def != null)
{
var next = def.GetContinueWith(ret);
var cont = continuation.Continuation;
if (cont != null)
{
if (cont.ContinuationIsFinally)
{
((TaskBase)next).Finally(cont);
}
else
{
next.Then(cont, cont.ContinuationAlways);
}
}
continuation.Then(next, continuationAlways);
}
}
public FuncTask<TResult, T> ContinueWithUI<T>(Func<bool, TResult, T> continuation, bool always = true)
public TaskBase(Task<TResult> task)
{
Task = task;
}
public override T Then<T>(T continuation, bool always = false)
{
deferred = null;
return base.Then<T>(continuation, always);
}
public ITask<T> ThenIf<T>(Func<TResult, ITask<T>> continueWith, bool always = false)
{
Guard.ArgumentNotNull(continueWith, "continueWith");
var ret = new FuncTask<TResult, T>(Token, (s, d) => default(T), this, always);
deferred = new DeferredContinuation { Always = always, GetContinueWith = d => continueWith(d) };
return ret;
}
public ITask<TResult> Finally(Func<bool, Exception, TResult, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent)
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new FuncTask<TResult, T>(Token, continuation, this) { Affinity = TaskAffinity.UI };
return ContinueWith(ret, always);
var ret = new FuncTask<TResult, TResult>(Token, continuation, this, true) { Affinity = affinity, Name = "Finally" };
ret.ContinuationIsFinally = true;
DependsOn?.SetFaultHandler(ret);
return ret;
}
public ITask Finally(Action<bool, Exception, TResult> continuation, TaskAffinity affinity = TaskAffinity.Concurrent)
{
Guard.ArgumentNotNull(continuation, "continuation");
var ret = new ActionTask<TResult>(Token, continuation, this, true) { Affinity = affinity, Name = "Finally" };
ret.ContinuationIsFinally = true;
DependsOn?.SetFaultHandler(ret);
return ret;
}
public new ITask<TResult> Start()
{
Task.Start(TaskManager.GetScheduler(Affinity));
base.Start();
return this;
}
public new ITask<TResult> Start(TaskScheduler scheduler)
{
Task.Start(scheduler);
base.Start(scheduler);
return this;
}
protected override void RunContinuation()
{
//if (deferred == null)
//{
base.RunContinuation();
//}
}
protected virtual TResult RunWithReturn(bool success)
{
base.Run(success);
return default(TResult);
}
@ -228,25 +367,37 @@ namespace GitHub.Unity
abstract class TaskBase<T, TResult> : TaskBase<TResult>
{
public TaskBase(CancellationToken token, ITask<T> dependsOn)
: base(token, dependsOn)
public TaskBase(CancellationToken token, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always)
{
Task = new Task<TResult>(() => RunWithData(dependsOn.Successful, dependsOn.Result), Token, TaskCreationOptions.None);
Task = new Task<TResult>(() =>
{
var ret = RunWithData(DependsOn?.Successful ?? previousSuccess, DependsOn.Successful ? ((ITask<T>)DependsOn).Result : default(T));
AdjustNextTask(ret);
return ret;
},
Token, TaskCreationOptions.None);
}
public TaskBase(Task<TResult> task)
: base(task)
{ }
protected virtual TResult RunWithData(bool success, T previousResult)
{
base.Run(success);
return default(TResult);
}
}
abstract class ListTaskBase<TResult, TData> : TaskBase<TResult>, ITask<TResult, TData>
abstract class DataTaskBase<TData, TResult> : TaskBase<TResult>, ITask<TData, TResult>
{
public ListTaskBase(CancellationToken token)
: base(token) { }
public DataTaskBase(CancellationToken token, ITask dependsOn = null, bool always = false)
: base(token, dependsOn, always) { }
public ListTaskBase(CancellationToken token, ITask dependsOn)
: base(token, dependsOn) { }
public DataTaskBase(Task<TResult> task)
: base(task)
{ }
public event Action<TData> OnData;
protected void RaiseOnData(TData data)
@ -255,10 +406,14 @@ namespace GitHub.Unity
}
}
abstract class ListTaskBase<T, TResult, TData> : TaskBase<T, TResult>, ITask<TResult, TData>
abstract class DataTaskBase<T, TData, TResult> : TaskBase<T, TResult>, ITask<TData, TResult>
{
public ListTaskBase(CancellationToken token, ITask<T> dependsOn)
: base(token, dependsOn) { }
public DataTaskBase(CancellationToken token, ITask<T> dependsOn = null, bool always = false)
: base(token, dependsOn, always) { }
public DataTaskBase(Task<TResult> task)
: base(task)
{ }
public event Action<TData> OnData;
protected void RaiseOnData(TData data)

Просмотреть файл

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@ -7,7 +6,25 @@ namespace GitHub.Unity
{
static class TaskExtensions
{
public static async Task<T> Catch<T>(this Task<T> source, Func<Exception, T> handler = null)
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "task")]
public static void Forget(this Task task)
{
}
public static async Task SafeAwait(this Task source, Action<Exception> handler = null)
{
try
{
await source;
}
catch (Exception ex)
{
if (handler != null)
handler(ex);
}
}
public static async Task<T> SafeAwait<T>(this Task<T> source, Func<Exception, T> handler = null)
{
try
{
@ -21,11 +38,11 @@ namespace GitHub.Unity
}
}
public static async Task Catch(this Task source, Action<Exception> handler = null)
public static async Task StartAwait(this ITask source, Action<Exception> handler = null)
{
try
{
await source;
await source.Start().Task;
}
catch (Exception ex)
{
@ -34,37 +51,24 @@ namespace GitHub.Unity
}
}
public static async Task<T> StartAwait<T>(this ITask<T> source, Func<Exception, T> handler = null)
{
try
{
return await source.Start().Task;
}
catch (Exception ex)
{
if (handler != null)
return handler(ex);
return default(T);
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "task")]
public static void Forget(this Task task)
public static void Forget(this ITask task)
{
}
public static async Task<ITask<T>> Catch<T>(this ITask<T> source, Func<ITask<T>, Exception, ITask<T>> handler = null)
{
try
{
await source.Task;
}
catch (Exception ex)
{
if (handler != null)
return handler(source, ex);
}
return source;
}
public static async Task<ITask> Catch(this ITask source, Action<ITask, Exception> handler = null)
{
try
{
await source.Task;
}
catch (Exception ex)
{
if (handler != null)
handler(source, ex);
}
return source;
task.Task.Forget();
}
//http://stackoverflow.com/a/29491927
@ -95,5 +99,103 @@ namespace GitHub.Unity
});
};
}
/// <summary>
/// Never end a chain with Catch, always use Finally instead
/// Catch sets a direct continuation on the previous task, which means await is not going to be
/// waiting for it to finish before returning (catch will run faster this way).
/// Always end a chain with Finally and use Catch statements in the middle of the chain.
/// </summary>
public static T Catch<T>(this T task, Action<Exception> handler)
where T : ITask
{
Guard.ArgumentNotNull(handler, "handler");
task.Task.ContinueWith(t =>
{
handler(t.Exception is AggregateException ? t.Exception.InnerException : t.Exception);
},
task.Token,
TaskContinuationOptions.OnlyOnFaulted,
TaskManager.GetScheduler(task.Affinity));
task.DependsOn?.Catch(handler);
return task;
}
public static ITask Then(this ITask task, Action<bool> continuation, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new ActionTask(task.Token, continuation, task, always) { Name = "Then" };
}
public static ActionTask<T> Then<T>(this ITask<T> task, Action<bool, T> continuation, TaskAffinity affinity = TaskAffinity.Concurrent, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new ActionTask<T>(task.Token, continuation, task, always) { Affinity = affinity, Name = $"Then<{typeof(T)}>" };
}
public static FuncTask<TResult, T> Then<TResult, T>(this ITask<TResult> task, Func<bool, TResult, T> continuation, TaskAffinity affinity = TaskAffinity.Concurrent, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new FuncTask<TResult, T>(task.Token, continuation, task) { Affinity = affinity, Name = $"Then<{typeof(TResult)}, {typeof(T)}>" };
}
public static FuncTask<T> Then<T>(this ITask task, Func<bool, T> continuation, TaskAffinity affinity = TaskAffinity.Concurrent, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new FuncTask<T>(task.Token, continuation, task) { Affinity = affinity, Name = $"Then<{typeof(T)}>" };
}
public static ITask<T> ThenAsync<T>(this ITask task, ITask<T> continuation, bool always = false)
{
return task.Then(continuation, always);
}
public static FuncTask<TResult, T> ThenInUI<TResult, T>(this ITask<TResult> task, Func<bool, TResult, T> continuation, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new FuncTask<TResult, T>(task.Token, continuation, task) { Affinity = TaskAffinity.UI, Name = $"ThenInUI<{typeof(TResult)}, {typeof(T)}>" };
}
public static ActionTask<T> ThenInUI<T>(this ITask<T> task, Action<bool, T> continuation, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new ActionTask<T>(task.Token, continuation, task) { Affinity = TaskAffinity.UI, Name = $"ThenInUI<{typeof(T)}>" };
}
public static ITask ThenInUI(this ITask task, ITask continuation)
{
Guard.ArgumentNotNull(continuation, "continuation");
continuation.Affinity = TaskAffinity.UI;
return continuation;
}
public static ITask ThenInUI(this ITask task, Action<bool> continuation, bool always = false)
{
Guard.ArgumentNotNull(continuation, "continuation");
return new ActionTask(task.Token, continuation, task, always) { Affinity = TaskAffinity.UI, Name = "ThenInUI" };
}
public static T FinallyInUI<T>(this T task, Action<bool, Exception> continuation)
where T : ITask
{
return (T)task.Finally(continuation, TaskAffinity.UI);
}
public static ITask<T> FinallyInUI<T>(this ITask<T> task, Func<bool, Exception, T, T> continuation)
{
return task.Finally(continuation, TaskAffinity.UI);
}
public static ITask FinallyInUI<T>(this ITask<T> task, Action<bool, Exception, T> continuation)
{
return task.Finally(continuation, TaskAffinity.UI);
}
public static ITask<T> ThenAsync<T>(this ITask task, Task<T> continuation, TaskAffinity affinity = TaskAffinity.Concurrent, bool always = false)
{
var cont = new FuncTask<T>(continuation) { Affinity = affinity, Name = $"ThenAsync<{typeof(T)}>" };
return task.Then(cont, always);
}
}
}

Просмотреть файл

@ -6,7 +6,7 @@ namespace GitHub.Unity
{
class TaskManager : ITaskManager
{
private static readonly ILogging logger = Logging.GetLogger<ProcessManager>();
private static readonly ILogging logger = Logging.GetLogger<TaskManager>();
private readonly CancellationTokenSource cts;
private readonly ConcurrentExclusiveInterleave manager;
@ -34,7 +34,12 @@ namespace GitHub.Unity
public void Stop()
{
cts.Cancel();
manager.Wait();
Wait();
}
public Task Wait()
{
return manager.Wait();
}
public static TaskScheduler GetScheduler(TaskAffinity affinity)
@ -100,13 +105,12 @@ namespace GitHub.Unity
{
task.Task.ContinueWith(tt =>
{
logger.Error(tt.Exception.InnerException, String.Format("Exception on ui thread: {0} {1}", tt.Id, task.Name));
logger.Trace(tt.Exception.InnerException, String.Format("Exception on ui thread: {0} {1}", tt.Id, task.Name));
},
cts.Token,
TaskContinuationOptions.OnlyOnFaulted, UIScheduler
TaskContinuationOptions.OnlyOnFaulted, ConcurrentScheduler
);
}
logger.Trace(String.Format("Schedule {0} {1}", "UI", task.Task.Id));
return (T)task.Start(UIScheduler);
}
@ -123,13 +127,12 @@ namespace GitHub.Unity
{
task.Task.ContinueWith(tt =>
{
logger.Error(tt.Exception.InnerException, String.Format("Exception on exclusive thread: {0} {1}", tt.Id, task.Name));
logger.Trace(tt.Exception.InnerException, String.Format("Exception on exclusive thread: {0} {1}", tt.Id, task.Name));
},
cts.Token,
TaskContinuationOptions.OnlyOnFaulted, UIScheduler
TaskContinuationOptions.OnlyOnFaulted, ConcurrentScheduler
);
}
logger.Trace(String.Format("Schedule {0} {1}", "Exclusive", task.Task.Id));
return (T)task.Start(manager.ExclusiveTaskScheduler);
}
@ -146,13 +149,12 @@ namespace GitHub.Unity
{
task.Task.ContinueWith(tt =>
{
logger.Error(tt.Exception.InnerException, String.Format("Exception on concurrent thread: {0} {1}", tt.Id, task.Name));
logger.Trace(tt.Exception.InnerException, String.Format("Exception on concurrent thread: {0} {1}", tt.Id, task.Name));
},
cts.Token,
TaskContinuationOptions.OnlyOnFaulted, UIScheduler
TaskContinuationOptions.OnlyOnFaulted, ConcurrentScheduler
);
}
logger.Trace(String.Format("Schedule {0} {1}", "Concurrent", task.Task.Id));
return (T)task.Start(manager.ConcurrentTaskScheduler);
}
}

Просмотреть файл

@ -150,7 +150,7 @@ namespace GitHub.Unity
{
app = String.Format("credential-{0} ", credHelper);
task = new ProcessTask<string>(taskManager.Token, app, new SimpleOutputProcessor())
.ConfigureGitProcess(processManager, true);
.Configure(processManager, true);
}
task.OnStart += t =>

Просмотреть файл

@ -2,8 +2,6 @@ using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace GitHub.Unity
{
@ -25,9 +23,10 @@ namespace GitHub.Unity
return FileSystem.FileExists(path);
}
public virtual async Task<string> FindGitInstallationPath(IProcessManager processManager)
public virtual ITask<NPath> FindGitInstallationPath(IProcessManager processManager)
{
return await LookForGitInPath(processManager);
return new ProcessTask<NPath>(TaskManager.Instance.Token, new FirstLineIsPathOutputProcessor())
.Configure(processManager, Environment.IsWindows ? "where" : "which", "git", null, false);
}
public abstract string GetExecutableExtension();
@ -144,13 +143,5 @@ namespace GitHub.Unity
psi.WorkingDirectory = workingDirectory;
}
protected async Task<string> LookForGitInPath(IProcessManager processManager)
{
return await new ProcessTask<string>(CancellationToken.None, Environment.IsWindows ? "where" : "which")
.ConfigureGitProcess(processManager)
.Task;
}
}
}

Просмотреть файл

@ -6,7 +6,7 @@ namespace GitHub.Unity
{
interface IProcessEnvironment
{
Task<string> FindGitInstallationPath(IProcessManager processManager);
ITask<NPath> FindGitInstallationPath(IProcessManager processManager);
string GetExecutableExtension();

Просмотреть файл

@ -10,10 +10,10 @@ namespace GitHub.Unity
{
}
public override Task<string> FindGitInstallationPath(IProcessManager processManager)
public override ITask<NPath> FindGitInstallationPath(IProcessManager processManager)
{
if (!String.IsNullOrEmpty(Environment.GitExecutablePath))
return TaskEx.FromResult(Environment.GitExecutablePath);
return new FuncTask<NPath>(TaskEx.FromResult(Environment.GitExecutablePath));
return base.FindGitInstallationPath(processManager); ;
}

Просмотреть файл

@ -11,27 +11,30 @@ namespace GitHub.Unity
{
}
public override async Task<string> FindGitInstallationPath(IProcessManager processManager)
public override ITask<NPath> FindGitInstallationPath(IProcessManager processManager)
{
Logger.Trace("Looking for Git Installation folder");
//if (!String.IsNullOrEmpty(Environment.GitExecutablePath))
// return Environment.GitExecutablePath;
var path = LookForPortableGit();
if (path == null)
{
path = await base.FindGitInstallationPath(processManager);
}
Logger.Trace("Git Installation folder {0} discovered: '{1}'", path == null ? "not" : "", path);
return path;
return new FuncTask<NPath>(TaskManager.Instance.Token, _ =>
{
Logger.Trace("Looking for Git Installation folder");
return LookForPortableGit();
})
.ThenIf(path =>
{
if (path == null)
return base.FindGitInstallationPath(processManager);
else
return new FuncTask<NPath>(TaskEx.FromResult(path));
})
.Finally((s, e, path) =>
{
Logger.Trace("Git Installation folder {0} discovered: '{1}'", path == null ? "not" : "", path);
return path;
});
}
private string LookForPortableGit()
private NPath LookForPortableGit()
{
NPath path = "/usr/local/bin/git";
NPath path = "/usr/local/bin/git".ToNPath();
if (path.FileExists())
return path;

Просмотреть файл

@ -28,7 +28,7 @@ namespace GitHub.Unity
{
GitEnvironment = new LinuxGitEnvironment(environment, filesystem);
localAppData = Environment.GetSpecialFolder(System.Environment.SpecialFolder.LocalApplicationData).ToNPath();
commonAppData = "/usr/local/share/";
commonAppData = "/usr/local/share/".ToNPath();
}
Environment.UserCachePath = localAppData.Combine(ApplicationInfo.ApplicationName);

Просмотреть файл

@ -13,44 +13,45 @@ namespace GitHub.Unity
{
}
public override async Task<string> FindGitInstallationPath(IProcessManager processManager)
public override ITask<NPath> FindGitInstallationPath(IProcessManager processManager)
{
if (!String.IsNullOrEmpty(Environment.GitExecutablePath))
return Environment.GitExecutablePath;
return new FuncTask<NPath>(TaskEx.FromResult(Environment.GitExecutablePath));
var path = LookForPortableGit();
if (path == null)
{
path = await base.FindGitInstallationPath(processManager);
}
Logger.Trace("Git Installation folder {0} discovered: '{1}'", path == null ? "not" : "", path);
return path;
return new FuncTask<NPath>(TaskManager.Instance.Token, _ =>
{
Logger.Trace("Looking for Git Installation folder");
return LookForPortableGit();
})
.ThenIf(path =>
{
if (path == null)
return base.FindGitInstallationPath(processManager);
else
return new FuncTask<NPath>(TaskEx.FromResult(path));
})
.Finally((s, e, path) =>
{
Logger.Trace("Git Installation folder {0} discovered: '{1}'", path == null ? "not" : "", path);
return path;
});
}
private string LookForPortableGit()
private NPath LookForPortableGit()
{
var localAppDataPath = Environment.GetSpecialFolder(System.Environment.SpecialFolder.LocalApplicationData);
var gitHubLocalAppDataPath = System.IO.Path.Combine(localAppDataPath, ApplicationInfo.ApplicationName);
var searchPath = System.IO.Path.Combine(gitHubLocalAppDataPath, "PortableGit_");
if (!FileSystem.DirectoryExists(gitHubLocalAppDataPath))
var gitHubLocalAppDataPath = Environment.UserCachePath;
if (!gitHubLocalAppDataPath.DirectoryExists())
return null;
var directories = FileSystem.GetDirectories(gitHubLocalAppDataPath).ToArray();
var searchPath = "PortableGit_";
var portableGitPath = directories.Where(s => {
var startsWith = s.StartsWith(searchPath, StringComparison.InvariantCultureIgnoreCase);
return startsWith;
}).Select(s => System.IO.Path.Combine(gitHubLocalAppDataPath, s)).ToArray().FirstOrDefault();
var portableGitPath = gitHubLocalAppDataPath.Directories()
.Where(s => s.FileName.StartsWith(searchPath, StringComparison.InvariantCultureIgnoreCase))
.FirstOrDefault();
if (portableGitPath != null)
{
portableGitPath = System.IO.Path.Combine(gitHubLocalAppDataPath, portableGitPath);
portableGitPath = System.IO.Path.Combine(portableGitPath, "cmd");
portableGitPath = System.IO.Path.Combine(portableGitPath, "git.exe");
portableGitPath = portableGitPath.Combine("cmd", $"git{GetExecutableExtension()}");
}
return portableGitPath;

Просмотреть файл

@ -4,7 +4,7 @@ namespace GitHub.Unity
{
interface IProcessManager
{
T ConfigureGitProcess<T>(T processTask, bool withInput = false) where T : IProcess;
T Configure<T>(T processTask, bool withInput = false) where T : IProcess;
T Configure<T>(T processTask, string executableFileName, string arguments, string workingDirectory = null, bool withInput = false)
where T : IProcess;
IProcess Reconnect(IProcess processTask, int i);

Просмотреть файл

@ -27,9 +27,12 @@ namespace GitHub.Unity
this.cancellationToken = cancellationToken;
}
public T ConfigureGitProcess<T>(T processTask, bool withInput = false) where T : IProcess
public T Configure<T>(T processTask, bool withInput = false) where T : IProcess
{
return Configure(processTask, environment.GitExecutablePath, processTask.ProcessArguments, environment.RepositoryPath, withInput);
return Configure(processTask,
processTask.ProcessName != null ? processTask.ProcessName : environment.GitExecutablePath,
processTask.ProcessArguments,
environment.RepositoryPath, withInput);
}
public T Configure<T>(T processTask, string executableFileName, string arguments, string workingDirectory = null, bool withInput = false)

Просмотреть файл

@ -0,0 +1,10 @@
using Rackspace.Threading;
namespace GitHub.Unity
{
class ProgressReport
{
public Progress<float> Percentage = new Progress<float>();
public Progress<long> Remaining = new Progress<long>();
}
}

Просмотреть файл

@ -34,7 +34,7 @@ namespace GitHub.Unity
/// <param name="name">The repository name.</param>
/// <param name="cloneUrl">The repository's clone URL.</param>
/// <param name="localPath"></param>
public Repository(IGitClient gitClient, IRepositoryManager repositoryManager, string name, UriString cloneUrl, string localPath)
public Repository(IGitClient gitClient, IRepositoryManager repositoryManager, string name, UriString cloneUrl, NPath localPath)
{
Guard.ArgumentNotNull(repositoryManager, nameof(repositoryManager));
Guard.ArgumentNotNullOrWhiteSpace(name, nameof(name));
@ -157,7 +157,7 @@ namespace GitHub.Unity
/// <returns></returns>
public override int GetHashCode()
{
return 17 * 23 + (Name?.GetHashCode() ?? 0) * 23 + (Owner?.GetHashCode() ?? 0) * 23 + (LocalPath?.TrimEnd('\\').ToUpperInvariant().GetHashCode() ?? 0);
return 17 * 23 + (Name?.GetHashCode() ?? 0) * 23 + (Owner?.GetHashCode() ?? 0) * 23 + (LocalPath?.GetHashCode() ?? 0);
}
public override bool Equals(object obj)
@ -181,7 +181,7 @@ namespace GitHub.Unity
String.Equals(Name, other.Name) &&
String.Equals(Owner, other.Owner) &&
String.Equals(CloneUrl, other.CloneUrl) &&
String.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase);
object.Equals(LocalPath, other.LocalPath);
}
/// <summary>
@ -208,7 +208,7 @@ namespace GitHub.Unity
public string Name { get; private set; }
public UriString CloneUrl { get; private set; }
public string LocalPath { get; private set; }
public NPath LocalPath { get; private set; }
public string Owner => CloneUrl?.Owner ?? string.Empty;
public bool IsGitHub { get { return CloneUrl != ""; } }

Просмотреть файл

@ -31,7 +31,7 @@ namespace GitHub.Unity
var initTask = new GitInitTask(token);
var unityYamlMergeExec = Environment.UnityApplication.ToNPath().Parent.Combine("Tools", "UnityYAMLMerge");
var unityYamlMergeExec = Environment.UnityApplication.Parent.Combine("Tools", "UnityYAMLMerge");
var yamlMergeCommand = string.Format(@"'{0}' merge -p ""$BASE"" ""$REMOTE"" ""$LOCAL"" ""$MERGED""", unityYamlMergeExec);
var yaml1 = new GitConfigSetTask("merge.unityyamlmerge.cmd", yamlMergeCommand, GitConfigSource.Local, token);
var yaml2 = new GitConfigSetTask("merge.unityyamlmerge.trustExitCode", "false", GitConfigSource.Local, token);
@ -59,13 +59,13 @@ namespace GitHub.Unity
var commitTask = new GitCommitTask("Initial commit", null, token);
initTask
.ContinueWith(yaml1)
.ContinueWith(yaml2)
.ContinueWith(lfsTask)
.ContinueWith(ignoresTask)
.ContinueWith(addTask)
.ContinueWith(commitTask)
.ContinueWith(_ => ApplicationManager.RestartRepository().Start(TaskManager.ConcurrentScheduler));
.Then(yaml1)
.Then(yaml2)
.Then(lfsTask)
.Then(ignoresTask)
.Then(addTask)
.Then(commitTask)
.Then(_ => ApplicationManager.RestartRepository().Start(TaskManager.ConcurrentScheduler));
initTask.Schedule(TaskManager);
//task.Critical = false;

Просмотреть файл

@ -68,7 +68,7 @@ namespace GitHub.Unity
DotGitPath =
DotGitPath.ReadAllLines()
.Where(x => x.StartsWith("gitdir:"))
.Select(x => x.Substring(7).Trim())
.Select(x => x.Substring(7).Trim().ToNPath())
.First();
}
@ -198,28 +198,26 @@ namespace GitHub.Unity
{
var add = GitClient.Add(files);
add.OnStart += t => IsBusy = true;
var task = add.ContinueWith(GitClient.Commit(message, body))
.ContinueWith(_ => IsBusy = false);
add.Schedule(taskManager);
return task;
return add.Then(GitClient.Commit(message, body))
.Then(_ => IsBusy = false);
}
public ITask Fetch(string remote)
{
var task = GitClient.Fetch(remote);
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask Pull(string remote, string branch)
{
var task = GitClient.Pull(remote, branch);
return HookupHandlers(task, true).Schedule(taskManager);
return HookupHandlers(task, true);
}
public ITask Push(string remote, string branch)
{
var task = GitClient.Push(remote, branch);
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask RemoteAdd(string remote, string url)
@ -228,9 +226,9 @@ namespace GitHub.Unity
HookupHandlers(task);
if (!platform.Environment.IsWindows)
{
task.ContinueWith(_ => OnConfigChanged());
task.Then(_ => OnConfigChanged());
}
return task.Schedule(taskManager);
return task;
}
public ITask RemoteRemove(string remote)
@ -239,40 +237,40 @@ namespace GitHub.Unity
HookupHandlers(task);
if (!platform.Environment.IsWindows)
{
task.ContinueWith(_ => OnConfigChanged());
task.Then(_ => OnConfigChanged());
}
return task.Schedule(taskManager);
return task;
}
public ITask RemoteChange(string remote, string url)
{
var task = GitClient.RemoteChange(remote, url);
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask SwitchBranch(string branch)
{
var task = GitClient.SwitchBranch(branch);
return HookupHandlers(task, true).Schedule(taskManager);
return HookupHandlers(task, true);
}
public ITask DeleteBranch(string branch, bool deleteUnmerged = false)
{
var task = GitClient.DeleteBranch(branch, deleteUnmerged);
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask CreateBranch(string branch, string baseBranch)
{
var task = GitClient.CreateBranch(branch, baseBranch);
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask ListLocks(bool local)
{
var task = GitClient
.ListLocks(local)
.ContinueWith((s, t) =>
.Then((s, t) =>
{
if (locks == null || !locks.SequenceEqual(t))
{
@ -281,13 +279,13 @@ namespace GitHub.Unity
OnLocksUpdated(locks);
}
});
return HookupHandlers(task).Schedule(taskManager);
return HookupHandlers(task);
}
public ITask LockFile(string file)
{
var task = GitClient.Lock(file);
HookupHandlers(task).Schedule(taskManager);
HookupHandlers(task);
ListLocks(false);
return task;
}
@ -355,7 +353,7 @@ namespace GitHub.Unity
Logger.Trace("Starting OnRepositoryUpdatedHandler");
var task = GitClient.Status()
.ContinueWith((success, data) =>
.Finally((success, ex, data) =>
{
if (success)
{
@ -363,7 +361,7 @@ namespace GitHub.Unity
}
Logger.Trace("Ending OnRepositoryUpdatedHandler");
});
HookupHandlers(task).Schedule(taskManager);
HookupHandlers(task).Start();
}
private void OnConfigChanged()

Просмотреть файл

@ -1,20 +0,0 @@
using System.Threading;
namespace GitHub.Unity
{
class FindGitTask : ProcessTask<string>
{
private readonly IEnvironment environment;
public FindGitTask(IEnvironment environment,
CancellationToken token, IOutputProcessor<string> processor = null, ITask dependsOn = null)
: base(token, processor ?? new FirstNonNullLineOutputProcessor(), dependsOn)
{
this.environment = environment;
}
public override string Name { get { return "find git"; } }
public override string ProcessName { get { return environment.IsWindows ? "where" : "which"; } }
public override string ProcessArguments { get { return "git"; } }
}
}

Просмотреть файл

@ -8,7 +8,6 @@ namespace GitHub.Unity
{
private readonly string value;
private readonly string arguments;
private bool done = false;
private string label;
public GitConfigSetTask(string key, string value, GitConfigSource configSource,

Просмотреть файл

@ -1,65 +0,0 @@
using System.Threading;
using FluentAssertions;
using GitHub.Unity;
using NUnit.Framework;
using Rackspace.Threading;
using TestUtils;
namespace IntegrationTests
{
class GitSetupTests : BaseGitRepoTest
{
[Test, Category("DoNotRunOnAppVeyor")]
public void InstallGit()
{
var environmentPath = NPath.CreateTempDirectory("integration-test-environment");
var environment = new IntegrationTestEnvironment(SolutionDirectory, environmentPath);
var gitSetup = new GitSetup(environment, FileSystem, CancellationToken.None);
var expectedPath = gitSetup.GitInstallationPath;
var setupDone = false;
var percent = -1f;
gitSetup.GitExecutablePath.FileExists().Should().BeFalse();
setupDone = gitSetup.SetupIfNeeded(percentage: new Progress<float>(x => percent = x)).Result;
if (environment.IsWindows)
{
environment.GitExecutablePath = gitSetup.GitExecutablePath;
setupDone.Should().BeTrue ();
percent.Should().Be (1);
Logger.Trace ("Expected GitExecutablePath: {0}", gitSetup.GitExecutablePath);
gitSetup.GitExecutablePath.FileExists ().Should ().BeTrue ();
var gitLfsDestinationPath = gitSetup.GitInstallationPath;
gitLfsDestinationPath = gitLfsDestinationPath.Combine("mingw32");
gitLfsDestinationPath = gitLfsDestinationPath.Combine("libexec", "git-core", "git-lfs.exe");
gitLfsDestinationPath.FileExists().Should().BeTrue();
var calculateMd5 = NPathFileSystemProvider.Current.CalculateMD5(gitLfsDestinationPath);
GitInstaller.GitLfsExecutableMD5.Should ().Be (calculateMd5);
setupDone = gitSetup.SetupIfNeeded (percentage: new Progress<float> (x => percent = x)).Result;
setupDone.Should ().BeFalse ();
}
else
{
environment.GitExecutablePath = "/usr/local/bin/git";
setupDone.Should().BeFalse ();
}
var platform = new Platform(environment, FileSystem);
var gitEnvironment = platform.GitEnvironment;
var processManager = new ProcessManager(environment, gitEnvironment);
var gitBranches = processManager.GetGitBranches(TestRepoMasterDirtyUnsynchronized, environment.GitExecutablePath).Result;
gitBranches.Should().BeEquivalentTo(
new GitBranch("master", "origin/master: behind 1", true),
new GitBranch("feature/document", "origin/feature/document", false));
}
}
}

Просмотреть файл

@ -1,16 +0,0 @@
using NSubstitute.Core;
namespace TestUtils
{
class CreateEnvironmentOptions
{
public const string DefaultExtensionFolder = @"c:\GitHubUnity\ExtensionFolder";
public const string DefaultUserProfilePath = @"c:\GitHubUnity\UserProfile";
public const string DefaultUnityProjectPathAndRepositoryPath = @"c:\GitHubUnity\UnityProject";
public string Extensionfolder { get; set; } = DefaultExtensionFolder;
public string UserProfilePath { get; set; } = DefaultUserProfilePath;
public string UnityProjectPath { get; set; } = DefaultUnityProjectPathAndRepositoryPath;
public string RepositoryPath { get; set; } = DefaultUnityProjectPathAndRepositoryPath;
}
}

Просмотреть файл

@ -23,7 +23,7 @@ namespace GitHub.Unity
{
Environment = environment;
FileSystem = fileSystem;
NPathFileSystemProvider.Current = FileSystem;
NPath.FileSystem = FileSystem;
Platform = platform;
ProcessManager = processManager;
}
@ -38,12 +38,12 @@ namespace GitHub.Unity
Initialize();
}
public override Task Run()
public override ITask Run()
{
Utility.Initialize();
return base.Run()
.ContinueWith(_ =>
.ThenInUI(_ =>
{
Utility.Run();
@ -54,14 +54,14 @@ namespace GitHub.Unity
view.Initialize(this);
//logger.Debug("Application Restarted");
}, UIScheduler);
}).Start();
}
protected override void InitializeEnvironment()
{
FileSystem = new FileSystem();
NPathFileSystemProvider.Current = FileSystem;
NPath.FileSystem = FileSystem;
Environment = new DefaultEnvironment();
@ -72,10 +72,10 @@ namespace GitHub.Unity
var assetsPath = Application.dataPath.ToNPath();
var projectPath = assetsPath.Parent;
Environment.UnityApplication = EditorApplication.applicationPath;
Environment.UnityApplication = EditorApplication.applicationPath.ToNPath();
Environment.UnityAssetsPath = assetsPath.ToString(SlashMode.Forward);
Environment.UnityProjectPath = projectPath.ToString(SlashMode.Forward);
Environment.UnityAssetsPath = assetsPath;
Environment.UnityProjectPath = projectPath;
base.InitializeEnvironment();
}
@ -132,17 +132,17 @@ namespace GitHub.Unity
}
}
private string DetermineInstallationPath()
private NPath DetermineInstallationPath()
{
// Juggling to find out where we got installed
var shim = ScriptableObject.CreateInstance<RunLocationShim>();
var script = MonoScript.FromScriptableObject(shim);
string ret = String.Empty;
NPath ret = null;
if (script != null)
{
var scriptPath = AssetDatabase.GetAssetPath(script).ToNPath();
ret = scriptPath.Parent.ToString(SlashMode.Forward);
ret = scriptPath.Parent;
}
ScriptableObject.DestroyImmediate(shim);
return ret;

Просмотреть файл

@ -167,7 +167,7 @@ namespace GitHub.Unity
public string TriggerText { get; private set; }
}
class EvaluateProjectConfigurationTask : ListTaskBase<List<ProjectConfigurationIssue>, ProjectConfigurationIssue>
class EvaluateProjectConfigurationTask : DataTaskBase<ProjectConfigurationIssue, List<ProjectConfigurationIssue>>
{
private const string GitIgnoreFilePattern = ".gitignore";
private const string VCSPropertyName = "m_ExternalVersionControlSupport";

Просмотреть файл

@ -512,7 +512,7 @@ namespace GitHub.Unity
if (createBranch)
{
GitClient.CreateBranch(newBranchName, selectedNode.Name)
.ContinueWithUI(success => { if (success) Refresh(); });
.FinallyInUI((success, e) => { if (success) Refresh(); });
}
// Cleanup
@ -617,7 +617,7 @@ namespace GitHub.Unity
ConfirmSwitchCancel))
{
GitClient.SwitchBranch(node.Name)
.ContinueWithUI(success =>
.FinallyInUI((success, e) =>
{
if (success)
Refresh();
@ -632,7 +632,7 @@ namespace GitHub.Unity
else if (node.Type == NodeType.RemoteBranch)
{
GitClient.CreateBranch(selectedNode.Name.Substring(selectedNode.Name.IndexOf('/') + 1), selectedNode.Name)
.ContinueWithUI(success =>
.FinallyInUI((success, e) =>
{
if (success)
Refresh();

Просмотреть файл

@ -190,8 +190,8 @@ namespace GitHub.Unity
busy = true;
GitClient.Commit(commitMessage, commitBody)
.ContinueWith(GitClient.Status())
.ContinueWithUI(_ => busy = false);
.Then(GitClient.Status())
.FinallyInUI((_, __) => busy = false);
}
}
}

Просмотреть файл

@ -159,7 +159,7 @@ namespace GitHub.Unity
{
var entryPath = entries[index].Path.ToNPath();
if (entryPath.IsChildOf(tree.Path))
entryPath = entryPath.RelativeTo(tree.Path);
entryPath = entryPath.RelativeTo(tree.Path.ToNPath());
var node = new FileTreeNode(entryPath, stateChangeCallback) { Target = entryCommitTargets[index] };
if (!string.IsNullOrEmpty(entries[index].ProjectPath))

Просмотреть файл

@ -127,7 +127,7 @@ namespace GitHub.Unity
private void RefreshLog()
{
GitClient.Log()
.ContinueWithUI((success, log) => { if (success) OnLogUpdate(log); });
.ThenInUI((success, log) => { if (success) OnLogUpdate(log); });
}
public override void Refresh()
@ -648,7 +648,7 @@ namespace GitHub.Unity
var remote = Repository.CurrentRemote.HasValue ? Repository.CurrentRemote.Value.Name : String.Empty;
Repository.Pull()
// we need the error propagated from the original git command to handle things appropriately
.ContinueWith(success =>
.Then(success =>
{
if (!success)
{
@ -656,8 +656,8 @@ namespace GitHub.Unity
// whether pull triggered a merge or a rebase, and abort the operation accordingly
// (either git rebase --abort or git merge --abort)
}
})
.ContinueWithUI(success => {
}, true)
.FinallyInUI((success, e) => {
if (success)
{
EditorUtility.DisplayDialog(Localization.PullActionTitle,
@ -670,15 +670,17 @@ namespace GitHub.Unity
Localization.PullFailureDescription,
Localization.Cancel);
}
});
})
.Start();
}
}
private void Push()
{
var remote = Repository.CurrentRemote.HasValue ? Repository.CurrentRemote.Value.Name : String.Empty;
Repository.Push()
.ContinueWithUI(success => {
Repository
.Push()
.FinallyInUI((success, e) => {
if (success)
{
EditorUtility.DisplayDialog(Localization.PushActionTitle,
@ -691,7 +693,8 @@ namespace GitHub.Unity
Localization.PushFailureDescription,
Localization.Cancel);
}
});
})
.Start();
}
void drawTimelineRectAroundIconRect(Rect parentRect, Rect iconRect)

Просмотреть файл

@ -47,7 +47,7 @@ namespace GitHub.Unity
if (locks == null)
return false;
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID());
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID()).ToNPath();
NPath repositoryPath = EntryPoint.Environment.GetRepositoryPath(assetPath);
var alreadyLocked = locks.Any(x =>
@ -69,16 +69,18 @@ namespace GitHub.Unity
isBusy = true;
var selected = Selection.activeObject;
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID());
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID()).ToNPath();
NPath repositoryPath = EntryPoint.Environment.GetRepositoryPath(assetPath);
repository.RequestLock(repositoryPath)
.ContinueWith(new ActionTask(EntryPoint.AppManager.CancellationToken, _ =>
repository
.RequestLock(repositoryPath)
.ThenInUI(new ActionTask(EntryPoint.AppManager.CancellationToken, _ =>
{
isBusy = false;
Selection.activeGameObject = null;
EditorApplication.RepaintProjectWindow();
}));
}))
.Start();
}
[MenuItem("Assets/Release lock", true, 1000)]
@ -95,7 +97,7 @@ namespace GitHub.Unity
if (locks == null || locks.Count == 0)
return false;
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID());
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID()).ToNPath();
NPath repositoryPath = EntryPoint.Environment.GetRepositoryPath(assetPath);
var isLocked = locks.Any(x => repositoryPath == x.Path);
@ -108,16 +110,18 @@ namespace GitHub.Unity
isBusy = true;
var selected = Selection.activeObject;
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID());
NPath assetPath = AssetDatabase.GetAssetPath(selected.GetInstanceID()).ToNPath();
NPath repositoryPath = EntryPoint.Environment.GetRepositoryPath(assetPath);
repository.ReleaseLock(repositoryPath, false)
.ContinueWith(new ActionTask(EntryPoint.AppManager.CancellationToken, _ =>
repository
.ReleaseLock(repositoryPath, false)
.ThenInUI(new ActionTask(EntryPoint.AppManager.CancellationToken, _ =>
{
isBusy = false;
Selection.activeGameObject = null;
EditorApplication.RepaintProjectWindow();
}));
}))
.Start();
}
public static void Run()
{
@ -159,7 +163,7 @@ namespace GitHub.Unity
guidsLocks.Clear();
foreach (var lck in locks)
{
NPath repositoryPath = lck.Path;
NPath repositoryPath = lck.Path.ToNPath();
NPath assetPath = EntryPoint.Environment.GetAssetPath(repositoryPath);
var g = AssetDatabase.AssetPathToGUID(assetPath);

Просмотреть файл

@ -127,7 +127,7 @@ namespace GitHub.Unity
lockedFiles = new List<GitLock>();
OnLocksUpdate(Repository.CurrentLocks);
Repository.OnLocksUpdated += RunLocksUpdateOnMainThread;
Repository.ListLocks();
Repository.ListLocks().Start();
gitName = Repository.User.Name;
gitEmail = Repository.User.Email;
@ -227,11 +227,11 @@ namespace GitHub.Unity
if (needsSaving)
{
GitClient.SetConfig("user.name", gitName, GitConfigSource.User)
.ContinueWith((success, value) => { if (success) Repository.User.Name = value; })
.ContinueWith(
.Then((success, value) => { if (success) Repository.User.Name = value; })
.Then(
GitClient.SetConfig("user.email", gitEmail, GitConfigSource.User)
.ContinueWith((success, value) => { if (success) Repository.User.Email = value; }))
.ContinueWithUI(_ => busy = false);
.Then((success, value) => { if (success) Repository.User.Email = value; }))
.FinallyInUI((_, __) => busy = false);
busy = true;
}
}
@ -255,7 +255,8 @@ namespace GitHub.Unity
if (needsSaving)
{
Repository.SetupRemote(repositoryRemoteName, repositoryRemoteUrl)
.ContinueWithUI(_ => busy = false, true);
.FinallyInUI((_, __) => busy = false)
.Start();
}
else
busy = false;
@ -583,7 +584,7 @@ namespace GitHub.Unity
GUILayout.FlexibleSpace();
if (GUILayout.Button("Unlock"))
{
Repository.ReleaseLock(lck.Path, false);
Repository.ReleaseLock(lck.Path, false).Start();
}
}
GUILayout.EndHorizontal();
@ -603,7 +604,7 @@ namespace GitHub.Unity
GUI.enabled = !busy;
var gitExecPath = EntryPoint.Environment.GitExecutablePath;
var gitExecPath = EntryPoint.Environment.GitExecutablePath.ToString();
// Install path field
EditorGUI.BeginChangeCheck();
{
@ -617,7 +618,7 @@ namespace GitHub.Unity
}
if (EditorGUI.EndChangeCheck())
{
EntryPoint.Environment.GitExecutablePath = gitExecPath;
EntryPoint.Environment.GitExecutablePath = gitExecPath.ToNPath();
}
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing);
@ -627,8 +628,9 @@ namespace GitHub.Unity
// Find button - for attempting to locate a new install
if (GUILayout.Button(GitInstallFindButton, GUILayout.ExpandWidth(false)))
{
var task = new FindGitTask(EntryPoint.Environment, Manager.CancellationToken)
.ContinueWithUI((success, path) =>
var task = new ProcessTask<NPath>(Manager.CancellationToken, new FirstLineIsPathOutputProcessor())
.Configure(Manager.ProcessManager, Manager.Environment.IsWindows ? "where" : "which", "git")
.FinallyInUI((success, ex, path) =>
{
if (success && !string.IsNullOrEmpty(path))
{

Просмотреть файл

@ -243,7 +243,7 @@ namespace GitHub.Unity
private void SignOut(object obj)
{
var task = new ActionTask(EntryPoint.CredentialManager.Delete(EntryPoint.CredentialManager.CachedCredentials.Host))
.ContinueWith(s =>
.Then(s =>
{
if (s)
{

Просмотреть файл

@ -21,7 +21,7 @@ namespace IntegrationTests
NPath commonParent = longestPath;
foreach (var path in paths)
{
var cp = commonParent.GetCommonParent(path);
var cp = commonParent.GetCommonParent(path.ToNPath());
if (cp != null)
commonParent = cp;
else
@ -37,7 +37,7 @@ namespace IntegrationTests
public void CommonParentTest()
{
var filesystem = new FileSystem(TestRepoMasterDirtyUnsynchronized);
NPathFileSystemProvider.Current = filesystem;
NPath.FileSystem = filesystem;
var environment = new DefaultEnvironment();
var ret = FindCommonPath(new string[]

Просмотреть файл

@ -1,29 +1,21 @@
using System.Linq;
using System.Threading;
using GitHub.Unity;
using NSubstitute;
using TestUtils;
namespace IntegrationTests
{
class BaseGitEnvironmentTest : BaseGitRepoTest
{
protected void InitializeEnvironment(NPath repoPath, bool enableEnvironmentTrace = false)
protected IEnvironment InitializeEnvironment(NPath repoPath, NPath environmentPath = null, bool enableEnvironmentTrace = false)
{
TaskManager = new TaskManager();
var sc = new ThreadSynchronizationContext(TaskManager.Token);
TaskManager.UIScheduler = new SynchronizationContextTaskScheduler(sc);
Environment = new IntegrationTestEnvironment(SolutionDirectory, enableTrace: enableEnvironmentTrace) {
Environment = new IntegrationTestEnvironment(SolutionDirectory, environmentPath, enableEnvironmentTrace) {
RepositoryPath = repoPath,
UnityProjectPath = repoPath
};
var gitSetup = new GitSetup(Environment, FileSystem, CancellationToken.None);
gitSetup.SetupIfNeeded().Wait();
Environment.GitExecutablePath = gitSetup.GitExecutablePath;
FileSystem.SetCurrentDirectory(repoPath);
Platform = new Platform(Environment, FileSystem);
@ -33,7 +25,6 @@ namespace IntegrationTests
Platform.Initialize(ProcessManager, TaskManager);
Environment.UnityProjectPath = repoPath;
Environment.GitExecutablePath = GitEnvironment.FindGitInstallationPath(ProcessManager).Result;
GitClient = new GitClient(Environment, ProcessManager, Platform.CredentialManager, TaskManager);
@ -51,7 +42,7 @@ namespace IntegrationTests
DotGitPath =
DotGitPath.ReadAllLines()
.Where(x => x.StartsWith("gitdir:"))
.Select(x => x.Substring(7).Trim())
.Select(x => x.Substring(7).Trim().ToNPath())
.First();
}
@ -60,6 +51,17 @@ namespace IntegrationTests
DotGitIndex = DotGitPath.Combine("index");
DotGitHead = DotGitPath.Combine("HEAD");
DotGitConfig = DotGitPath.Combine("config");
return Environment;
}
protected IEnvironment Initialize(NPath repoPath, NPath environmentPath = null, bool enableEnvironmentTrace = false)
{
InitializeEnvironment(repoPath, environmentPath, enableEnvironmentTrace);
var gitSetup = new GitSetup(Environment, FileSystem, TaskManager.Token);
gitSetup.SetupIfNeeded().Wait();
Environment.GitExecutablePath = gitSetup.GitExecutablePath;
return Environment;
}
protected override void OnTearDown()

Просмотреть файл

@ -1,8 +1,6 @@
using System.IO;
using System.Threading;
using GitHub.Unity;
using Ionic.Zip;
using NUnit.Framework;
namespace IntegrationTests
{
@ -17,10 +15,7 @@ namespace IntegrationTests
TestRepoMasterCleanSynchronized = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_clean_sync");
TestRepoMasterDirtyUnsynchronized = TestBasePath.Combine("IOTestsRepo", "IOTestsRepo_master_dirty_unsync");
using (var zipFile = new ZipFile(TestZipFilePath))
{
zipFile.ExtractAll(TestBasePath.ToString(), ExtractExistingFileAction.OverwriteSilently);
}
ZipHelper.ExtractZipFile(TestZipFilePath, TestBasePath.ToString(), CancellationToken.None);
}
protected NPath TestRepoMasterCleanSynchronized { get; private set; }

Просмотреть файл

@ -3,6 +3,7 @@ using FluentAssertions;
using NUnit.Framework;
using GitHub.Unity;
using NCrunch.Framework;
using System.Threading;
namespace IntegrationTests
{
@ -13,7 +14,7 @@ namespace IntegrationTests
protected ILogging Logger { get; private set; }
protected IFileSystem FileSystem { get; private set; }
protected TestUtils.SubstituteFactory Factory { get; set; }
protected static string SolutionDirectory => TestContext.CurrentContext.TestDirectory;
protected static NPath SolutionDirectory => TestContext.CurrentContext.TestDirectory.ToNPath();
[TestFixtureSetUp]
public void TestFixtureSetUp()
@ -30,11 +31,8 @@ namespace IntegrationTests
protected virtual void OnSetup()
{
FileSystem = new FileSystem(TestBasePath);
NPathFileSystemProvider.Current.Should().BeNull("Test should run in isolation");
NPathFileSystemProvider.Current = FileSystem;
FileSystem = new FileSystem();
NPath.FileSystem = FileSystem;
TestBasePath = NPath.CreateTempDirectory("integration-tests");
FileSystem.SetCurrentDirectory(TestBasePath);
}
@ -47,18 +45,25 @@ namespace IntegrationTests
protected virtual void OnTearDown()
{
try
TaskManager.Instance?.Stop();
Logger.Debug("Deleting TestBasePath: {0}", TestBasePath.ToString());
for (var i = 0; i < 5; i++)
{
Logger.Debug("Deleting TestBasePath: {0}", TestBasePath.ToString());
TestBasePath.Delete();
try
{
TestBasePath.Delete();
break;
}
catch (Exception)
{
Thread.Sleep(100);
}
}
catch (Exception)
{
if (TestBasePath.Exists())
Logger.Warning("Error deleting TestBasePath: {0}", TestBasePath.ToString());
}
FileSystem = null;
NPathFileSystemProvider.Current = null;
NPath.FileSystem = null;
}
}
}

Просмотреть файл

@ -1,21 +1,21 @@
using System;
using System.Collections.Generic;
using System.Threading;
using FluentAssertions;
using GitHub.Unity;
using NSubstitute;
using NUnit.Framework;
using TestUtils;
using TestUtils.Events;
using System.Threading.Tasks;
namespace IntegrationTests
{
class RepositoryManagerTests : BaseGitEnvironmentTest
{
[Test]
public void ShouldDetectFileChanges()
public async Task ShouldDetectFileChanges()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -41,6 +41,11 @@ namespace IntegrationTests
var foobarTxt = TestRepoMasterCleanSynchronized.Combine("foobar.txt");
foobarTxt.WriteAllText("foobar");
await TaskManager.Wait();
// give the fs watcher a bit of time to catch up
await TaskEx.Delay(200);
await TaskManager.Wait();
managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
Logger.Trace("Continue test");
@ -59,9 +64,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldAddAndCommitFiles()
public async Task ShouldAddAndCommitFiles()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -102,6 +107,7 @@ namespace IntegrationTests
var testDocumentTxt = TestRepoMasterCleanSynchronized.Combine("Assets", "TestDocument.txt");
testDocumentTxt.WriteAllText("foobar");
await TaskManager.Wait();
managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(5000)).Should().BeTrue();
@ -123,7 +129,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.CommitFiles(new List<string>() { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty);
Assert.DoesNotThrow(async () => await RepositoryManager.CommitFiles(new List<string>() { "Assets\\TestDocument.txt", "foobar.txt" }, "IntegrationTest Commit", string.Empty).Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(5)).Should().BeTrue();
@ -139,9 +146,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchChange()
public async Task ShouldDetectBranchChange()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -159,10 +166,15 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.SwitchBranch("feature/document");
Assert.DoesNotThrow(async () => await RepositoryManager.SwitchBranch("feature/document").Start().Task);
await TaskManager.Wait();
// give the fs watcher a bit of time to catch up
await TaskEx.Delay(100);
await TaskManager.Wait();
managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();
managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();
managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();
managerAutoResetEvent.OnHeadChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();
Logger.Trace("Continue test");
@ -180,9 +192,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchDelete()
public async Task ShouldDetectBranchDelete()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -191,7 +203,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.DeleteBranch("feature/document", true);
Assert.DoesNotThrow(async () => await RepositoryManager.DeleteBranch("feature/document", true).Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
managerAutoResetEvent.OnRemoteOrTrackingChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -210,9 +223,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchCreate()
public async Task ShouldDetectBranchCreate()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -221,7 +234,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.CreateBranch("feature/document2", "feature/document");
Assert.DoesNotThrow(async () => await RepositoryManager.CreateBranch("feature/document2", "feature/document").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -241,7 +255,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.CreateBranch("feature2/document2", "feature/document");
Assert.DoesNotThrow(async () => await RepositoryManager.CreateBranch("feature2/document2", "feature/document").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnLocalBranchListChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -260,9 +275,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectChangesToRemotes()
public async Task ShouldDetectChangesToRemotes()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -271,7 +286,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.RemoteRemove("origin").Wait();
Assert.DoesNotThrow(async () => await RepositoryManager.RemoteRemove("origin").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
managerAutoResetEvent.OnActiveRemoteChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -293,7 +309,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.RemoteAdd("origin", "https://github.com/EvilStanleyGoldman/IOTestsRepo.git").Wait();
Assert.DoesNotThrow(async () => await RepositoryManager.RemoteAdd("origin", "https://github.com/EvilStanleyGoldman/IOTestsRepo.git").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnActiveRemoteChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
managerAutoResetEvent.OnRemoteOrTrackingChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -311,10 +328,10 @@ namespace IntegrationTests
repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock);
}
[Test, Ignore("Failing on CI, needs fixing")]
public void ShouldDetectGitPull()
[Test]
public async Task ShouldDetectGitPull()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -332,7 +349,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.Pull("origin", "master").Wait();
Assert.DoesNotThrow(async () => await RepositoryManager.Pull("origin", "master").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnRepositoryChanged.WaitOne(TimeSpan.FromSeconds(7)).Should().BeTrue();
managerAutoResetEvent.OnActiveBranchChanged.WaitOne(TimeSpan.FromSeconds(7)).Should().BeTrue();
@ -357,10 +375,10 @@ namespace IntegrationTests
repositoryManagerListener.DidNotReceive().OnLocksUpdated(Args.EnumerableGitLock);
}
[Test, Ignore("Failing on CI, needs fixing")]
public void ShouldDetectGitFetch()
[Test]
public async Task ShouldDetectGitFetch()
{
InitializeEnvironment(TestRepoMasterCleanUnsynchronized);
Initialize(TestRepoMasterCleanUnsynchronized);
var managerAutoResetEvent = new RepositoryManagerAutoResetEvent();
@ -369,7 +387,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
RepositoryManager.Fetch("origin").Wait();
Assert.DoesNotThrow(async () => await RepositoryManager.Fetch("origin").Start().Task);
await TaskManager.Wait();
managerAutoResetEvent.OnRemoteBranchListChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();
managerAutoResetEvent.OnRemoteBranchListChanged.WaitOne(TimeSpan.FromSeconds(3)).Should().BeTrue();

Просмотреть файл

@ -5,6 +5,7 @@ using GitHub.Unity;
using NSubstitute;
using NUnit.Framework;
using TestUtils;
using System.Threading.Tasks;
namespace IntegrationTests
{
@ -13,9 +14,9 @@ namespace IntegrationTests
private const int ThreadSleepTimeout = 2000;
[Test]
public void ShouldDetectFileChanges()
public async Task ShouldDetectFileChanges()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -34,6 +35,7 @@ namespace IntegrationTests
Logger.Trace("Issuing Changes");
foobarTxt.WriteAllText("foobar");
await TaskManager.Wait();
watcherAutoResetEvent.IndexChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.RepositoryChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -58,9 +60,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchChange()
public async Task ShouldDetectBranchChange()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -76,7 +78,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
SwitchBranch("feature/document");
Assert.DoesNotThrow(async () => await GitClient.SwitchBranch("feature/document").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.HeadChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.IndexChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -102,9 +105,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchDelete()
public async Task ShouldDetectBranchDelete()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -120,7 +123,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
DeleteBranch("feature/document");
Assert.DoesNotThrow(async () => await GitClient.DeleteBranch("feature/document", true).Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.ConfigChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.LocalBranchDeleted.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -145,9 +149,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectBranchCreate()
public async Task ShouldDetectBranchCreate()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -163,7 +167,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
CreateBranch("feature/document2", "feature/document");
Assert.DoesNotThrow(async () => await GitClient.CreateBranch("feature/document2", "feature/document").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.LocalBranchCreated.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -184,7 +189,8 @@ namespace IntegrationTests
Logger.Trace("Issuing Command");
CreateBranch("feature2/document2", "feature/document");
Assert.DoesNotThrow(async () => await GitClient.CreateBranch("feature2/document2", "feature/document").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.LocalBranchCreated.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -210,9 +216,9 @@ namespace IntegrationTests
}
[Test]
public void ShouldDetectChangesToRemotes()
public async Task ShouldDetectChangesToRemotes()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -228,7 +234,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
GitRemoteRemove("origin");
Assert.DoesNotThrow(async () => await GitClient.RemoteRemove("origin").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.ConfigChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.RemoteBranchDeleted.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -253,7 +260,11 @@ namespace IntegrationTests
Logger.Trace("Issuing 2nd Command");
GitRemoteAdd("origin", "https://github.com/EvilStanleyGoldman/IOTestsRepo.git");
Assert.DoesNotThrow(async () => await GitClient.RemoteAdd("origin", "https://github.com/EvilStanleyGoldman/IOTestsRepo.git").Start().Task);
// give the fs watcher a bit of time to catch up
await TaskEx.Delay(500);
await TaskManager.Wait();
watcherAutoResetEvent.ConfigChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
Logger.Trace("Continue 2nd test");
@ -275,10 +286,10 @@ namespace IntegrationTests
}
}
[Test, Ignore("Failing on CI, needs fixing")]
public void ShouldDetectGitPull()
[Test]
public async Task ShouldDetectGitPull()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanSynchronized);
@ -294,7 +305,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
GitPull("origin", "master");
Assert.DoesNotThrow(async () => await GitClient.Pull("origin", "master").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.IndexChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.LocalBranchChanged.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
@ -319,10 +331,10 @@ namespace IntegrationTests
}
}
[Test, Ignore("Failing on CI, needs fixing")]
public void ShouldDetectGitFetch()
[Test]
public async Task ShouldDetectGitFetch()
{
InitializeEnvironment(TestRepoMasterCleanUnsynchronized);
Initialize(TestRepoMasterCleanUnsynchronized);
var repositoryWatcher = CreateRepositoryWatcher(TestRepoMasterCleanUnsynchronized);
@ -338,7 +350,8 @@ namespace IntegrationTests
{
Logger.Trace("Issuing Command");
GitFetch("origin");
Assert.DoesNotThrow(async () => await GitClient.Fetch("origin").Start().Task);
await TaskManager.Wait();
watcherAutoResetEvent.RemoteBranchCreated.WaitOne(TimeSpan.FromSeconds(2)).Should().BeTrue();
watcherAutoResetEvent.RemoteBranchCreated.WaitOne(TimeSpan.FromSeconds(2));
@ -363,69 +376,6 @@ namespace IntegrationTests
}
}
protected void SwitchBranch(string branch)
{
var evt = new ManualResetEventSlim(false);
GitClient.SwitchBranch(branch)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void GitPull(string remote, string branch)
{
var evt = new ManualResetEventSlim(false);
GitClient.Pull(remote, branch)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void GitFetch(string remote)
{
var evt = new ManualResetEventSlim(false);
GitClient.Fetch(remote)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void DeleteBranch(string branch)
{
var evt = new ManualResetEventSlim(false);
GitClient.DeleteBranch(branch)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void GitRemoteAdd(string remote, string url)
{
var evt = new ManualResetEventSlim(false);
GitClient.RemoteAdd(remote, url)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void GitRemoteRemove(string remote)
{
var evt = new ManualResetEventSlim(false);
GitClient.RemoteRemove(remote)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
protected void CreateBranch(string branch, string baseBranch)
{
var evt = new ManualResetEventSlim(false);
GitClient.CreateBranch(branch, baseBranch)
.ContinueWithUI(_ => evt.Set());
var completed = evt.Wait(100);
completed.Should().BeTrue();
}
private RepositoryWatcher CreateRepositoryWatcher(NPath path)
{
var paths = new RepositoryPathConfiguration(path);

Просмотреть файл

@ -0,0 +1,67 @@
using System.Threading;
using FluentAssertions;
using GitHub.Unity;
using NUnit.Framework;
using Rackspace.Threading;
using TestUtils;
using System.Threading.Tasks;
namespace IntegrationTests
{
class GitSetupTests : BaseGitEnvironmentTest
{
[Test, Category("DoNotRunOnAppVeyor")]
public async Task InstallGit()
{
var environmentPath = NPath.CreateTempDirectory("integration-test-environment");
var environment = InitializeEnvironment(TestRepoMasterDirtyUnsynchronized, environmentPath);
var gitSetup = new GitSetup(environment, FileSystem, TaskManager.Token);
var expectedPath = gitSetup.GitInstallationPath;
var setupDone = false;
var percent = -1f;
gitSetup.GitExecutablePath.FileExists().Should().BeFalse();
setupDone = await gitSetup.SetupIfNeeded(percentage: new Progress<float>(x => percent = x));
if (environment.IsWindows)
{
environment.GitExecutablePath = gitSetup.GitExecutablePath;
setupDone.Should().BeTrue();
percent.Should().Be(1);
Logger.Trace("Expected GitExecutablePath: {0}", gitSetup.GitExecutablePath);
gitSetup.GitExecutablePath.FileExists().Should().BeTrue();
var gitLfsDestinationPath = gitSetup.GitInstallationPath;
gitLfsDestinationPath = gitLfsDestinationPath.Combine("mingw32");
gitLfsDestinationPath = gitLfsDestinationPath.Combine("libexec", "git-core", "git-lfs.exe");
gitLfsDestinationPath.FileExists().Should().BeTrue();
var calculateMd5 = NPath.FileSystem.CalculateMD5(gitLfsDestinationPath);
GitInstaller.GitLfsExecutableMD5.Should().Be(calculateMd5);
setupDone = await gitSetup.SetupIfNeeded(percentage: new Progress<float>(x => percent = x));
setupDone.Should().BeFalse();
}
else
{
environment.GitExecutablePath = "/usr/local/bin/git".ToNPath();
setupDone.Should().BeFalse();
}
var platform = new Platform(environment, FileSystem);
var gitEnvironment = platform.GitEnvironment;
var processManager = new ProcessManager(environment, gitEnvironment);
var gitBranches = await processManager.GetGitBranches(TestRepoMasterDirtyUnsynchronized, environment.GitExecutablePath);
gitBranches.Should().BeEquivalentTo(
new GitBranch("master", "origin/master: behind 1", true),
new GitBranch("feature/document", "origin/feature/document", false));
}
}
}

Просмотреть файл

@ -8,11 +8,11 @@ namespace IntegrationTests
private static readonly ILogging logger = Logging.GetLogger<IntegrationTestEnvironment>();
private readonly bool enableTrace;
private readonly string extensionInstallPath;
private readonly NPath extensionInstallPath;
private readonly NPath integrationTestEnvironmentPath;
private DefaultEnvironment defaultEnvironment;
private string unityProjectPath;
private NPath unityProjectPath;
public IntegrationTestEnvironment(NPath solutionDirectory, NPath environmentPath = null,
bool enableTrace = false)
@ -65,10 +65,10 @@ namespace IntegrationTests
public string UserProfilePath => integrationTestEnvironmentPath.CreateDirectory("user-profile-path");
public string Path => Environment.GetEnvironmentVariable("PATH");
public NPath Path => Environment.GetEnvironmentVariable("PATH").ToNPath();
public string NewLine => Environment.NewLine;
public string GitExecutablePath
public NPath GitExecutablePath
{
get { return defaultEnvironment.GitExecutablePath; }
set
@ -85,11 +85,11 @@ namespace IntegrationTests
public bool IsLinux => defaultEnvironment.IsLinux;
public bool IsMac => defaultEnvironment.IsMac;
public string UnityApplication { get; set; }
public NPath UnityApplication { get; set; }
public string UnityAssetsPath { get; set; }
public NPath UnityAssetsPath { get; set; }
public string UnityProjectPath
public NPath UnityProjectPath
{
get { return unityProjectPath; }
set
@ -102,7 +102,7 @@ namespace IntegrationTests
}
}
public string ExtensionInstallPath
public NPath ExtensionInstallPath
{
get { return extensionInstallPath; }
set {}
@ -111,9 +111,9 @@ namespace IntegrationTests
public NPath UserCachePath { get; set; }
public NPath SystemCachePath { get; set; }
public string RepositoryPath { get; set; }
public NPath RepositoryPath { get; set; }
public string GitInstallPath
public NPath GitInstallPath
{
get { return defaultEnvironment.GitInstallPath; }
}

Просмотреть файл

@ -10,7 +10,7 @@ namespace IntegrationTests
[Test]
public void FindRepoRootTest()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var repositoryLocator = new RepositoryLocator(Environment.UnityProjectPath);

Просмотреть файл

@ -32,19 +32,19 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="AsyncBridge.Net35, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b3b1c0202c0d6a87, processorArchitecture=MSIL">
<HintPath>..\..\packages\AsyncBridge.Net35.0.2.0\lib\net35-Client\AsyncBridge.Net35.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="DotNetZip, Version=1.10.1.0, Culture=neutral, PublicKeyToken=6583c7c814667745, processorArchitecture=MSIL">
<HintPath>..\..\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll</HintPath>
<HintPath>$(SolutionDir)\packages\AsyncBridge.Net35.0.2.0\lib\net35-Client\AsyncBridge.Net35.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)packages\FluentAssertions.2.2.0.0\lib\net35\FluentAssertions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="ICSharpCode.SharpZipLib, Version=2.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\5.5\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="NCrunch.Framework, Version=3.3.0.6, Culture=neutral, PublicKeyToken=01d101bf6f3e0aea, processorArchitecture=MSIL">
<HintPath>..\..\packages\NCrunch.Framework.3.3.0.6\lib\NCrunch.Framework.dll</HintPath>
<HintPath>$(SolutionDir)\packages\NCrunch.Framework.3.3.0.6\lib\NCrunch.Framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NSubstitute, Version=1.10.0.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
@ -87,11 +87,11 @@
<Compile Include="ThreadSynchronizationContext.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj">
<ProjectReference Include="$(SolutionDir)\src\\GitHub.Api\GitHub.Api.csproj">
<Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project>
<Name>GitHub.Api</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj">
<ProjectReference Include="$(SolutionDir)\src\\GitHub.Logging\GitHub.Logging.csproj">
<Project>{bb6a8eda-15d8-471b-a6ed-ee551e0b3ba0}</Project>
<Name>GitHub.Logging</Name>
</ProjectReference>
@ -99,7 +99,7 @@
<Project>{66a1d219-f61d-4ae4-9bd7-aaeb97276fff}</Project>
<Name>TestUtils</Name>
</ProjectReference>
<ProjectReference Include="..\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.csproj">
<ProjectReference Include="$(SolutionDir)\src\\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.csproj">
<Project>{add7a18b-dd2a-4c22-a2c1-488964eff30a}</Project>
<Name>GitHub.Unity</Name>
</ProjectReference>

Просмотреть файл

@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using GitHub.Unity;
using TestUtils;
using System.Threading.Tasks;
namespace IntegrationTests
{
@ -14,7 +14,7 @@ namespace IntegrationTests
[Test]
public void BranchListTest()
{
InitializeEnvironment(TestRepoMasterCleanUnsynchronized);
Initialize(TestRepoMasterCleanUnsynchronized);
var gitBranches = ProcessManager.GetGitBranches(TestRepoMasterCleanUnsynchronized).Result;
@ -26,13 +26,11 @@ namespace IntegrationTests
[Test]
public void LogEntriesTest()
{
InitializeEnvironment(TestRepoMasterCleanUnsynchronized);
Initialize(TestRepoMasterCleanUnsynchronized);
var logEntries =
ProcessManager
.GetGitLogEntries(TestRepoMasterCleanUnsynchronized, Environment, FileSystem, GitEnvironment, 2)
.Result
.ToArray();
List<GitLogEntry> logEntries = null;
Assert.DoesNotThrow(async () => logEntries = await ProcessManager
.GetGitLogEntries(TestRepoMasterCleanUnsynchronized, Environment, FileSystem, GitEnvironment, 2));
logEntries.AssertEqual(new[]
{
@ -78,7 +76,7 @@ namespace IntegrationTests
[Test, Category("DoNotRunOnAppVeyor")]
public void RussianLogEntriesTest()
{
InitializeEnvironment(TestRepoMasterCleanUnsynchronizedRussianLanguage);
Initialize(TestRepoMasterCleanUnsynchronizedRussianLanguage);
var logEntries =
ProcessManager
@ -112,7 +110,7 @@ namespace IntegrationTests
[Test]
public void RemoteListTest()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var gitRemotes = ProcessManager.GetGitRemoteEntries(TestRepoMasterCleanSynchronized).Result;
@ -128,7 +126,7 @@ namespace IntegrationTests
[Test]
public void StatusTest()
{
InitializeEnvironment(TestRepoMasterDirtyUnsynchronized);
Initialize(TestRepoMasterDirtyUnsynchronized);
var gitStatus = ProcessManager
.GetGitStatus(TestRepoMasterDirtyUnsynchronized, Environment, FileSystem, GitEnvironment)
@ -162,7 +160,7 @@ namespace IntegrationTests
[Test]
public void CredentialHelperGetTest()
{
InitializeEnvironment(TestRepoMasterCleanSynchronized);
Initialize(TestRepoMasterCleanSynchronized);
var s = ProcessManager.GetGitCreds(TestRepoMasterCleanSynchronized, Environment, FileSystem, GitEnvironment);
s.Should().NotBeNull();

Просмотреть файл

@ -8,16 +8,14 @@ namespace IntegrationTests
{
static class ProcessManagerExtensions
{
static NPath defaultGitPath = "git".ToNPath();
public static async Task<IEnumerable<GitBranch>> GetGitBranches(this IProcessManager processManager,
string workingDirectory,
string gitPath = null)
NPath workingDirectory,
NPath gitPath = null)
{
var processor = new BranchListOutputProcessor();
NPath path = null;
if (gitPath == null)
path = "git";
else
path = gitPath;
NPath path = gitPath ?? defaultGitPath;
var results = await new ProcessTaskWithListOutput<GitBranch>(CancellationToken.None, processor)
.Configure(processManager, path, "branch -vv", workingDirectory, false)
@ -27,10 +25,11 @@ namespace IntegrationTests
return results;
}
public static async Task<List<GitLogEntry>> GetGitLogEntries(this IProcessManager processManager,
string workingDirectory,
public static Task<List<GitLogEntry>> GetGitLogEntries(this IProcessManager processManager,
NPath workingDirectory,
IEnvironment environment, IFileSystem filesystem, IProcessEnvironment gitEnvironment,
int? logCount = null, string gitPath = null)
int? logCount = null,
NPath gitPath = null)
{
var gitStatusEntryFactory = new GitObjectFactory(environment);
@ -43,33 +42,23 @@ namespace IntegrationTests
logNameStatus = logNameStatus + " -" + logCount.Value;
}
NPath path = null;
if (gitPath == null)
path = "git";
else
path = gitPath;
NPath path = gitPath ?? defaultGitPath;
var results = await new ProcessTaskWithListOutput<GitLogEntry>(CancellationToken.None, processor)
return new ProcessTaskWithListOutput<GitLogEntry>(CancellationToken.None, processor)
.Configure(processManager, path, logNameStatus, workingDirectory, false)
.Start()
.Task;
return results;
}
public static async Task<GitStatus> GetGitStatus(this IProcessManager processManager,
string workingDirectory,
IEnvironment environment, IFileSystem filesystem, IProcessEnvironment gitEnvironment,
string gitPath = null)
NPath gitPath = null)
{
var gitStatusEntryFactory = new GitObjectFactory(environment);
var processor = new StatusOutputProcessor(gitStatusEntryFactory);
NPath path = null;
if (gitPath == null)
path = "git";
else
path = gitPath;
NPath path = gitPath ?? defaultGitPath;
var results = await new ProcessTask<GitStatus?>(CancellationToken.None, processor)
.Configure(processManager, path, "status -b -u --porcelain", workingDirectory, false)
@ -81,15 +70,11 @@ namespace IntegrationTests
public static async Task<List<GitRemote>> GetGitRemoteEntries(this IProcessManager processManager,
string workingDirectory,
string gitPath = null)
NPath gitPath = null)
{
var processor = new RemoteListOutputProcessor();
NPath path = null;
if (gitPath == null)
path = "git";
else
path = gitPath;
NPath path = gitPath ?? defaultGitPath;
var results = await new ProcessTaskWithListOutput<GitRemote>(CancellationToken.None, processor)
.Configure(processManager, path, "remote -v", workingDirectory, false)
@ -101,15 +86,11 @@ namespace IntegrationTests
public static async Task<string> GetGitCreds(this IProcessManager processManager,
string workingDirectory,
IEnvironment environment, IFileSystem filesystem, IProcessEnvironment gitEnvironment,
string gitPath = null)
NPath gitPath = null)
{
var processor = new FirstNonNullLineOutputProcessor();
NPath path = null;
if (gitPath == null)
path = "git";
else
path = gitPath;
NPath path = gitPath ?? defaultGitPath;
var task = new ProcessTask<string>(CancellationToken.None, processor)
.Configure(processManager, path, "credential-wincred get", workingDirectory, true);

Просмотреть файл

@ -18,7 +18,7 @@ namespace IntegrationTests
Logging.LoggerFactory = context => new MultipleLogAdapter(() => new ConsoleLogAdapter(context),
() => new FileLogAdapter(fileLog, context));
Logging.Trace("Logging to \"{0}\"", fileLog);
Logging.Debug("Logging to \"{0}\"", fileLog);
}
}
}

Просмотреть файл

@ -62,7 +62,7 @@ namespace GitHub.Unity
count++;
if (current - secondStart > TimeSpan.TicksPerMillisecond * 1000)
{
Console.WriteLine(String.Format("FPS {0}", count));
//Console.WriteLine(String.Format("FPS {0}", count));
count = 0;
secondStart = current;
}
@ -71,7 +71,11 @@ namespace GitHub.Unity
long waitTime = (current + ticksPerFrame - lastTime) / TimeSpan.TicksPerMillisecond;
if (waitTime > 0 && waitTime < int.MaxValue)
{
wait.Wait((int)waitTime, token);
try
{
wait.Wait((int)waitTime, token);
}
catch { }
}
}
}
@ -85,6 +89,7 @@ namespace GitHub.Unity
}
if (queue.TryDequeue(out data))
{
Logging.GetLogger<ThreadSynchronizationContext>().Trace($"Running {data.Id} on main thread");
data.Run();
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("IntegrationTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1a382f40-fd9e-43e1-89c1-320073f35ce9")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("IntegrationTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("IntegrationTests")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("1a382f40-fd9e-43e1-89c1-320073f35ce9")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,73 @@
//--------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: SynchronizationContextTaskScheduler.cs
//
//--------------------------------------------------------------------------
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
namespace System.Threading.Tasks.Schedulers
{
/// <summary>Provides a task scheduler that targets a specific SynchronizationContext.</summary>
public sealed class SynchronizationContextTaskScheduler : TaskScheduler
{
/// <summary>The queue of tasks to execute, maintained for debugging purposes.</summary>
private readonly ConcurrentQueue<Task> _tasks;
/// <summary>The target context under which to execute the queued tasks.</summary>
private readonly SynchronizationContext _context;
/// <summary>Initializes an instance of the SynchronizationContextTaskScheduler class.</summary>
public SynchronizationContextTaskScheduler() :
this(SynchronizationContext.Current)
{
}
/// <summary>
/// Initializes an instance of the SynchronizationContextTaskScheduler class
/// with the specified SynchronizationContext.
/// </summary>
/// <param name="context">The SynchronizationContext under which to execute tasks.</param>
public SynchronizationContextTaskScheduler(SynchronizationContext context)
{
if (context == null) throw new ArgumentNullException("context");
_context = context;
_tasks = new ConcurrentQueue<Task>();
}
/// <summary>Queues a task to the scheduler for execution on the I/O ThreadPool.</summary>
/// <param name="task">The Task to queue.</param>
protected override void QueueTask(Task task)
{
_tasks.Enqueue(task);
_context.Post(delegate
{
Task nextTask;
if (_tasks.TryDequeue(out nextTask)) TryExecuteTask(nextTask);
}, null);
}
/// <summary>Tries to execute a task on the current thread.</summary>
/// <param name="task">The task to be executed.</param>
/// <param name="taskWasPreviouslyQueued">Ignored.</param>
/// <returns>Whether the task could be executed.</returns>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
return _context == SynchronizationContext.Current && TryExecuteTask(task);
}
/// <summary>Gets an enumerable of tasks queued to the scheduler.</summary>
/// <returns>An enumerable of tasks queued to the scheduler.</returns>
protected override IEnumerable<Task> GetScheduledTasks()
{
return _tasks.ToArray();
}
/// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
public override int MaximumConcurrencyLevel { get { return 1; } }
}
}

Просмотреть файл

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{1A382F40-FD9E-43E1-89C1-320073F35CE9}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>IntegrationTests</RootNamespace>
<AssemblyName>TaskSystemIntegrationTests</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="AsyncBridge.Net35, Version=0.0.0.0, Culture=neutral, PublicKeyToken=b3b1c0202c0d6a87, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\AsyncBridge.Net35.0.2.0\lib\net35-Client\AsyncBridge.Net35.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NSubstitute, Version=1.10.0.0, Culture=neutral, PublicKeyToken=92dd2e9066daa5ca, processorArchitecture=MSIL">
<HintPath>..\..\..\packages\NSubstitute.1.10.0.0\lib\net35\NSubstitute.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Rackspace.Threading, Version=2.0.0.0, Culture=neutral, PublicKeyToken=bb62785d398726f0, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Rackspace.Threading.2.0.0-alpha001\lib\net35-client\Rackspace.Threading.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Threading, Version=1.0.2856.102, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\TaskParallelLibrary.1.0.2856.0\lib\Net35\System.Threading.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SynchronizationContextTaskScheduler.cs" />
<Compile Include="Tests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ThreadSynchronizationContext.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\TestApp\TestApp.csproj">
<Project>{08b87d2a-8cf1-4211-b7aa-5209f00f72f8}</Project>
<Name>TestApp</Name>
</ProjectReference>
<ProjectReference Include="..\..\GitHub.Api\GitHub.Api.csproj">
<Project>{B389ADAF-62CC-486E-85B4-2D8B078DF763}</Project>
<Name>GitHub.Api</Name>
</ProjectReference>
<ProjectReference Include="..\..\GitHub.Logging\GitHub.Logging.csproj">
<Project>{bb6a8eda-15d8-471b-a6ed-ee551e0b3ba0}</Project>
<Name>GitHub.Logging</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -0,0 +1,635 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using System.Threading;
using GitHub.Unity;
using System.Threading.Tasks.Schedulers;
using System.IO;
using NSubstitute;
namespace IntegrationTests
{
class BaseTest
{
protected ITaskManager TaskManager { get; set; }
protected IProcessManager ProcessManager { get; set; }
protected NPath TestBasePath { get; private set; }
protected IFileSystem FileSystem { get; private set; }
protected CancellationToken Token => TaskManager.Token;
protected NPath TestApp => System.Reflection.Assembly.GetExecutingAssembly().Location.ToNPath().Combine("TestApp.exe");
[TestFixtureSetUp]
public void OneTimeSetup()
{
Logging.LoggerFactory = context => new ConsoleLogAdapter(context);
TaskManager = new TaskManager();
var syncContext = new ThreadSynchronizationContext(Token);
TaskManager.UIScheduler = new SynchronizationContextTaskScheduler(syncContext);
FileSystem = new FileSystem();
NPath.FileSystem = FileSystem;
TestBasePath = NPath.CreateTempDirectory("integration-tests");
FileSystem.SetCurrentDirectory(TestBasePath);
var env = new DefaultEnvironment();
var repo = Substitute.For<IRepository>();
repo.LocalPath.Returns(TestBasePath);
env.Repository = repo;
var platform = new Platform(env, FileSystem);
ProcessManager = new ProcessManager(env, platform.GitEnvironment, Token);
var processEnv = platform.GitEnvironment;
var path = processEnv.FindGitInstallationPath(ProcessManager).Start().Result;
env.GitExecutablePath = path;
}
[TestFixtureTearDown]
public void OneTimeTearDown()
{
TaskManager?.Stop();
try
{
TestBasePath.DeleteIfExists();
}
catch { }
}
[SetUp]
public void Setup()
{
}
[TearDown]
public void Teardown()
{
}
}
[TestFixture]
class ProcessTaskTests : BaseTest
{
[Test]
public async Task ProcessReturningErrorThrowsException()
{
var success = false;
Exception thrown = null;
var output = new List<string>();
var expectedOutput = new List<string> { "one name" };
var task = new SimpleProcessTask(TestApp, @"-s 100 -d ""one name""", Token).Configure(ProcessManager)
.Catch(ex => thrown = ex)
.Then((s, d) => output.Add(d))
.Then(new SimpleProcessTask(TestApp, @"-e kaboom -r -1", Token).Configure(ProcessManager))
.Catch(ex => thrown = ex)
.Then((s, d) => output.Add(d))
.Finally((s, e) => success = s);
await task.StartAwait();
Assert.IsFalse(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNotNull(thrown);
Assert.AreEqual("kaboom", thrown.Message);
}
}
[TestFixture]
class SchedulerTests : BaseTest
{
private ActionTask GetTask(TaskAffinity affinity, int id, Action<int> body, ActionTask dependsOn = null)
{
return new ActionTask(Token, _ => body(id), dependsOn) { Affinity = affinity };
}
/// <summary>
/// This exemplifies that running a bunch of tasks that don't depend on anything on the concurrent (default) scheduler
/// run in any order
/// </summary>
[Test]
public void ConcurrentSchedulerDoesNotGuaranteeOrdering()
{
var runningOrder = new List<int>();
var rand = new Randomizer();
var tasks = new List<ActionTask>();
for (int i = 1; i < 11; i++)
{
tasks.Add(GetTask(TaskAffinity.Concurrent, i, id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); }));
}
TaskManager.Schedule(tasks.Cast<ITask>().ToArray());
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
Console.WriteLine(String.Join(",", runningOrder.Select(x => x.ToString()).ToArray()));
Assert.AreEqual(10, runningOrder.Count);
}
/// <summary>
/// This exemplifies that running a bunch of tasks that depend on other things on the concurrent (default) scheduler
/// run in dependency order. Each group of tasks depends on a task on the previous group, so the first group
/// runs first, then the second group of tasks, then the third. Run order within each group is not guaranteed
/// </summary>
[Test]
public void ConcurrentSchedulerWithDependencyOrdering()
{
var count = 3;
var runningOrder = new List<int>();
var rand = new Randomizer();
var startTasks = new List<ActionTask>();
var i = 1;
for (var start = i; i < start + count; i++)
{
startTasks.Add(GetTask(TaskAffinity.Concurrent, i,
id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); }));
}
var midTasks = new List<ActionTask>();
for (var start = i; i < start + count; i++)
{
midTasks.Add(GetTask(TaskAffinity.Concurrent, i,
id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); },
startTasks[i - 4]));
}
var endTasks = new List<ActionTask>();
for (var start = i; i < start + count; i++)
{
endTasks.Add(GetTask(TaskAffinity.Concurrent, i,
id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); },
midTasks[i - 7]));
}
foreach (var t in endTasks)
t.Start();
Task.WaitAll(endTasks.Select(x => x.Task).ToArray());
CollectionAssert.AreEquivalent(Enumerable.Range(1, 3), runningOrder.Take(3));
CollectionAssert.AreEquivalent(Enumerable.Range(4, 3), runningOrder.Skip(3).Take(3));
CollectionAssert.AreEquivalent(Enumerable.Range(7, 3), runningOrder.Skip(6).Take(3));
}
[Test]
public void ExclusiveSchedulerGuaranteesOrdering()
{
var runningOrder = new List<int>();
var tasks = new List<ActionTask>();
var rand = new Randomizer();
for (int i = 1; i < 11; i++)
{
tasks.Add(GetTask(TaskAffinity.Exclusive, i, id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); }));
}
TaskManager.Schedule(tasks.Cast<ITask>().ToArray());
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
Assert.AreEqual(Enumerable.Range(1, 10), runningOrder);
}
[Test]
public void UISchedulerGuaranteesOrdering()
{
var runningOrder = new List<int>();
var tasks = new List<ActionTask>();
var rand = new Randomizer();
for (int i = 1; i < 11; i++)
{
tasks.Add(GetTask(TaskAffinity.UI, i, id => { new ManualResetEventSlim().Wait(rand.Next(100, 200)); lock (runningOrder) runningOrder.Add(id); }));
}
TaskManager.Schedule(tasks.Cast<ITask>().ToArray());
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
Assert.AreEqual(Enumerable.Range(1, 10), runningOrder);
}
[Test]
public async void NonUITasksAlwaysRunOnDifferentThreadFromUITasks()
{
var output = new Dictionary<int, int>();
var tasks = new List<ITask>();
var seed = Randomizer.RandomSeed;
var rand = new Randomizer(seed);
var uiThread = 0;
await new ActionTask(Token, _ => uiThread = Thread.CurrentThread.ManagedThreadId) { Affinity = TaskAffinity.UI }.StartAwait();
for (int i = 1; i < 100; i++)
{
tasks.Add(GetTask(i % 2 == 0 ? TaskAffinity.Concurrent : TaskAffinity.Exclusive, i,
id => { lock (output) output.Add(id, Thread.CurrentThread.ManagedThreadId); })
.Start());
}
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
CollectionAssert.DoesNotContain(output.Values, uiThread);
}
[Test]
public async void ChainingOnDifferentSchedulers()
{
var output = new Dictionary<int, KeyValuePair<int, int>>();
var tasks = new List<ITask>();
var seed = Randomizer.RandomSeed;
var rand = new Randomizer(seed);
var uiThread = 0;
await new ActionTask(Token, _ => uiThread = Thread.CurrentThread.ManagedThreadId) { Affinity = TaskAffinity.UI }.StartAwait();
for (int i = 1; i < 100; i++)
{
tasks.Add(
GetTask(TaskAffinity.UI, i,
id => { lock (output) output.Add(id, KeyValuePair.Create(Thread.CurrentThread.ManagedThreadId, -1)); })
.Then(
GetTask(i % 2 == 0 ? TaskAffinity.Concurrent : TaskAffinity.Exclusive, i,
id => { lock (output) output[id] = KeyValuePair.Create(output[id].Key, Thread.CurrentThread.ManagedThreadId); })
).
Start());
}
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
Console.WriteLine(String.Join(",", output.Select(x => x.Key.ToString()).ToArray()));
foreach (var t in output)
{
Assert.AreEqual(uiThread, t.Value.Key, $"Task {t.Key} pass 1 should have been on ui thread {uiThread} but ran instead on {t.Value.Key}");
Assert.AreNotEqual(t.Value.Key, t.Value.Value, $"Task {t.Key} pass 2 should not have been on ui thread {uiThread}");
}
}
}
[TestFixture]
class Chains : BaseTest
{
[Test]
public async Task ThrowingInterruptsTaskChainButAlwaysRunsFinallyAndCatch()
{
var success = false;
string thrown = "";
Exception finallyException = null;
var output = new List<string>();
var expectedOutput = new List<string> { "one name" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(_ => { throw new Exception("an exception"); })
.Catch(ex => thrown = ex.Message)
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.ThenInUI((s, d) => output.Add(d))
.Finally((s, e) =>
{
success = s;
finallyException = e;
});
await task.StartAwait();
Assert.IsFalse(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNotNull(finallyException);
}
[Test]
public async Task FinallyReportsException()
{
var success = false;
Exception finallyException = null;
var output = new List<string>();
var expectedOutput = new List<string> { "one name" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(_ => { throw new Exception("an exception"); })
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.ThenInUI((s, d) => output.Add(d))
.Finally((s, e) =>
{
success = s;
finallyException = e;
});
await task.StartAwait();
Assert.IsFalse(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNotNull(finallyException);
Assert.AreEqual("an exception", finallyException.Message);
}
[Test]
public async Task CatchAlwaysRunsBeforeFinally()
{
var success = false;
Exception exception = null;
Exception finallyException = null;
var runOrder = new List<string>();
var output = new List<string>();
var expectedOutput = new List<string> { "one name" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(_ => { throw new Exception("an exception"); })
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.Then((s, d) =>
{
output.Add(d);
return "done";
})
.Catch(ex =>
{
Thread.Sleep(300);
lock (runOrder)
{
exception = ex;
runOrder.Add("catch");
}
})
.Finally((s, e, d) =>
{
Thread.Sleep(300);
lock (runOrder)
{
success = s;
finallyException = e;
runOrder.Add("finally");
}
return d;
});
await task.StartAwait();
Assert.IsFalse(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNotNull(exception);
Assert.IsNotNull(finallyException);
Assert.AreEqual("an exception", exception.Message);
Assert.AreEqual("an exception", finallyException.Message);
CollectionAssert.AreEqual(new List<string> { "catch", "finally" }, runOrder);
}
[Test]
public async Task DoNotUseCatchAtTheEndOfAChain()
{
var success = false;
Exception exception = null;
var output = new List<string>();
var expectedOutput = new List<string> { "one name" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(_ => { throw new Exception("an exception"); })
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.ThenInUI((s, d) => output.Add(d))
.Finally((_, __) => { })
.Catch(ex => { Thread.Sleep(20); exception = ex; });
await task.StartAwait();
Assert.IsFalse(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNull(exception);
}
[Test]
public async Task FinallyCanReturnData()
{
var success = false;
Exception exception = null;
Exception finallyException = null;
var runOrder = new List<string>();
var output = new List<string>();
var expectedOutput = new List<string> { "one name", "another name", "done" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.Then((s, d) =>
{
output.Add(d);
return "done";
})
.Catch(ex =>
{
lock (runOrder)
{
exception = ex;
runOrder.Add("catch");
}
})
.Finally((s, e, d) =>
{
lock (runOrder)
{
success = s;
output.Add(d);
finallyException = e;
runOrder.Add("finally");
}
return d;
});
var ret = await task.StartAwait();
Assert.AreEqual("done", ret);
Assert.IsTrue(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNull(exception);
Assert.IsNull(finallyException);
CollectionAssert.AreEqual(new List<string> { "finally" }, runOrder);
}
[Test]
public void ConditionalChaining()
{
var success = false;
Exception exception = null;
Exception finallyException = null;
var runOrder = new List<string>();
var output = new List<string>();
var bools = new List<bool>();
for (int i = 0; i < 10; i++)
{
bools.Add(i % 2 == 0);
}
var expectedOutput = bools.SelectMany(x => new List<string> { x.ToString().ToLower(), x ? "something" : "nothing" }).ToList();
var tasks = new List<ITask>();
foreach (var b in bools)
{
var task =
new FuncTask<bool>(Token, _ => b)
.ThenIf(go =>
{
output.Add(go.ToString().ToLower());
if (go)
return new FuncTask<string>(Token, _ => "something");
else
return new FuncTask<string>(Token, _ => "nothing");
})
.Finally((s, e, d) =>
{
lock (runOrder)
{
success = s;
output.Add(d);
finallyException = e;
}
return d;
});
tasks.Add(task.Start());
}
Task.WaitAll(tasks.Select(x => x.Task).ToArray());
Assert.IsTrue(success);
CollectionAssert.AreEquivalent(expectedOutput, output);
Assert.IsNull(exception);
Assert.IsNull(finallyException);
}
[Test]
public async Task FinallyCanAlsoNotReturnData()
{
var success = false;
Exception exception = null;
Exception finallyException = null;
var runOrder = new List<string>();
var output = new List<string>();
var expectedOutput = new List<string> { "one name", "another name", "done" };
var task =
new FuncTask<string>(Token, _ => "one name") { Affinity = TaskAffinity.UI }
.Then((s, d) => output.Add(d))
.Then(new FuncTask<string>(Token, _ => "another name") { Affinity = TaskAffinity.Exclusive })
.Then((s, d) =>
{
output.Add(d);
return "done";
})
.Finally((s, e, d) =>
{
lock (runOrder)
{
success = s;
output.Add(d);
finallyException = e;
runOrder.Add("finally");
}
});
await task.StartAwait();
Assert.IsTrue(success);
CollectionAssert.AreEqual(expectedOutput, output);
Assert.IsNull(exception);
Assert.IsNull(finallyException);
CollectionAssert.AreEqual(new List<string> { "finally" }, runOrder);
}
}
[TestFixture]
class Exceptions : BaseTest
{
[Test]
public async Task StartAndEndAreAlwaysRaised()
{
var runOrder = new List<string>();
var task = new ActionTask(Token, _ => { throw new Exception(); })
.Finally((s, d) => { });
task.OnStart += _ => runOrder.Add("start");
task.OnEnd += _ => runOrder.Add("end");
await task.Start().Task;
CollectionAssert.AreEqual(new string[] { "start", "end" }, runOrder);
}
[Test]
[ExpectedException(typeof(InvalidOperationException))]
public async Task ExceptionPropagatesOutIfNoFinally()
{
var runOrder = new List<string>();
var task = new ActionTask(Token, _ => { throw new InvalidOperationException(); })
.Catch(_ => { });
await task.Start().Task;
}
[Test]
public async Task StartAwaitSafelyAwaits()
{
var runOrder = new List<string>();
var task = new ActionTask(Token, _ => { throw new InvalidOperationException(); })
.Catch(_ => { });
await task.StartAwait();
}
}
[TestFixture]
class TaskToActionTask : BaseTest
{
[Test]
public async Task CanWrapATask()
{
var uiThread = 0;
await new ActionTask(Token, _ => uiThread = Thread.CurrentThread.ManagedThreadId) { Affinity = TaskAffinity.UI }.StartAwait();
var runOrder = new List<string>();
var task = new Task(() => runOrder.Add($"ran {Thread.CurrentThread.ManagedThreadId}"));
var act = new ActionTask(task) { Affinity = TaskAffinity.UI };
await act.StartAwait();
CollectionAssert.AreEqual(new string[] { $"ran {uiThread}" }, runOrder);
}
[Test]
public async Task AsyncBodies()
{
var uiThread = 0;
await new ActionTask(Token, _ => uiThread = Thread.CurrentThread.ManagedThreadId) { Affinity = TaskAffinity.UI }.StartAwait();
var runOrder = new List<string>();
var act = new ActionTask(Token, _ => runOrder.Add($"ran 1"))
.ThenAsync(new Task<int>(() =>
{
runOrder.Add($"ran 2");
return 10;
}))
.ThenAsync(new FuncTask<int, int>(Token, (s, n) =>
{
runOrder.Add($"ran 3");
return n * 2;
}))
;
var ret = await act.StartAwait();
CollectionAssert.AreEqual(new string[] { "ran 1", "ran 2", "ran 3" }, runOrder);
Assert.AreEqual(20, ret);
}
[Test]
public async Task Inlining()
{
var runOrder = new List<string>();
var act = new ActionTask(Token, _ => runOrder.Add($"started"))
.ThenAsync(TaskEx.FromResult(1), TaskAffinity.Exclusive)
.Then((_, n) => n + 1)
.Then((_, n) => runOrder.Add(n.ToString()))
.ThenAsync(TaskEx.FromResult(20f), TaskAffinity.Exclusive)
.Then((_, n) => n + 1)
.Then((_, n) => runOrder.Add(n.ToString()))
.Finally((_, t) => runOrder.Add("done"))
;
await act.StartAwait();
CollectionAssert.AreEqual(new string[] { "started", "2", "21", "done" }, runOrder);
}
}
static class KeyValuePair
{
public static KeyValuePair<TKey, TValue> Create<TKey, TValue>(TKey key, TValue value)
{
return new KeyValuePair<TKey, TValue>(key, value);
}
}
}

Просмотреть файл

@ -0,0 +1,138 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace GitHub.Unity
{
class ThreadSynchronizationContext : SynchronizationContext
{
private readonly CancellationToken token;
private readonly ConcurrentQueue<PostData> queue = new ConcurrentQueue<PostData>();
private readonly ConcurrentQueue<PostData> priorityQueue = new ConcurrentQueue<PostData>();
private readonly JobSignal jobSignal = new JobSignal();
private long jobId;
private readonly Task task;
private int threadId;
public ThreadSynchronizationContext(CancellationToken token)
{
this.token = token;
task = new Task(Start, token, TaskCreationOptions.LongRunning);
task.Start();
}
public override void Post(SendOrPostCallback d, object state)
{
queue.Enqueue(new PostData { Callback = d, State = state });
}
public override void Send(SendOrPostCallback d, object state)
{
if (Thread.CurrentThread.ManagedThreadId == threadId)
{
d(state);
}
else
{
var id = Interlocked.Increment(ref jobId);
priorityQueue.Enqueue(new PostData { Id = id, Callback = d, State = state });
Wait(id);
}
}
private void Wait(long id)
{
jobSignal.Wait(id, token);
}
private void Start()
{
threadId = Thread.CurrentThread.ManagedThreadId;
var lastTime = DateTime.Now.Ticks;
var wait = new ManualResetEventSlim(false);
var ticksPerFrame = TimeSpan.TicksPerMillisecond * 10;
var count = 0;
var secondStart = DateTime.Now.Ticks;
while (!token.IsCancellationRequested)
{
var current = DateTime.Now.Ticks;
var elapsed = current - lastTime;
count++;
if (current - secondStart > TimeSpan.TicksPerMillisecond * 1000)
{
//Console.WriteLine(String.Format("FPS {0}", count));
count = 0;
secondStart = current;
}
Pump();
lastTime = DateTime.Now.Ticks;
long waitTime = (current + ticksPerFrame - lastTime) / TimeSpan.TicksPerMillisecond;
if (waitTime > 0 && waitTime < int.MaxValue)
{
try
{
wait.Wait((int)waitTime, token);
}
catch {}
}
}
}
public void Pump()
{
PostData data;
if (priorityQueue.TryDequeue(out data))
{
data.Run();
}
if (queue.TryDequeue(out data))
{
Logging.GetLogger<ThreadSynchronizationContext>().Trace($"Running {data.Id} on main thread");
data.Run();
}
}
struct PostData
{
public long Id;
public SendOrPostCallback Callback;
public object State;
public void Run()
{
Callback(State);
}
}
class JobSignal : ManualResetEventSlim
{
private readonly HashSet<long> signaledIds = new HashSet<long>();
public void Set(long id)
{
try
{
signaledIds.Add(id);
}
catch { } // it's already on the list
Set();
Reset();
}
public bool Wait(long id, CancellationToken token)
{
bool signaled = false;
do
{
signaled = signaledIds.Contains(id);
if (signaled)
break;
Wait(token);
}
while (!token.IsCancellationRequested && !signaled);
return signaled;
}
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="AsyncBridge.Net35" version="0.2.0" targetFramework="net35" />
<package id="NSubstitute" version="1.10.0.0" targetFramework="net35" />
<package id="NUnit" version="2.6.4" targetFramework="net35" />
<package id="NUnitTestAdapter" version="2.1.1" targetFramework="net35" />
<package id="Rackspace.Threading" version="2.0.0-alpha001" targetFramework="net35" />
<package id="TaskParallelLibrary" version="1.0.2856.0" targetFramework="net452" />
</packages>

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v2.0.50727"/></startup>
</configuration>

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,39 @@
using System;
using Mono.Options;
using System.IO;
using System.Threading;
namespace TestApp
{
class Program
{
static int Main(string[] args)
{
int retCode = 0;
string ret = String.Empty;
string error = String.Empty;
int sleepms = 0;
var p = new OptionSet();
p = p
.Add("r=", (int v) => retCode = v)
.Add("d=|data=", v => ret = v)
.Add("e=|error=", v => error = v)
.Add("f=|file=", v => ret = File.ReadAllText(v))
.Add("ef=|errorFile=", v => error = File.ReadAllText(v))
.Add("s=|sleep=", (int v) => sleepms = v)
.Add("h|help", v => p.WriteOptionDescriptions(Console.Out));
p.Parse(args);
if (sleepms > 0)
Thread.Sleep(sleepms);
if (!String.IsNullOrEmpty(ret))
Console.WriteLine(ret);
if (!String.IsNullOrEmpty(error))
Console.Error.WriteLine(error);
return retCode;
}
}
}

Просмотреть файл

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TestApp")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TestApp")]
[assembly: AssemblyCopyright("Copyright © 2017")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("08b87d2a-8cf1-4211-b7aa-5209f00f72f8")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Просмотреть файл

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{08B87D2A-8CF1-4211-B7AA-5209F00F72F8}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>TestApp</RootNamespace>
<AssemblyName>TestApp</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="Mono.Options-PCL.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

Просмотреть файл

@ -34,7 +34,8 @@ namespace TestUtils.Events
static class RepositoryManagerListenerExtensions
{
public static void AttachListener(this IRepositoryManagerListener listener, IRepositoryManager repositoryManager, RepositoryManagerAutoResetEvent managerAutoResetEvent = null, bool trace = false)
public static void AttachListener(this IRepositoryManagerListener listener, IRepositoryManager repositoryManager,
RepositoryManagerAutoResetEvent managerAutoResetEvent = null, bool trace = false)
{
var logger = trace ? Logging.GetLogger<IRepositoryManagerListener>() : null;

Просмотреть файл

Просмотреть файл

@ -0,0 +1,17 @@
using GitHub.Unity;
using NSubstitute.Core;
namespace TestUtils
{
class CreateEnvironmentOptions
{
public const string DefaultExtensionFolder = @"c:\GitHubUnity\ExtensionFolder";
public const string DefaultUserProfilePath = @"c:\GitHubUnity\UserProfile";
public const string DefaultUnityProjectPathAndRepositoryPath = @"c:\GitHubUnity\UnityProject";
public NPath Extensionfolder { get; set; } = DefaultExtensionFolder.ToNPath();
public NPath UserProfilePath { get; set; } = DefaultUserProfilePath.ToNPath();
public NPath UnityProjectPath { get; set; } = DefaultUnityProjectPathAndRepositoryPath.ToNPath();
public NPath RepositoryPath { get; set; } = DefaultUnityProjectPathAndRepositoryPath.ToNPath();
}
}

Просмотреть файл

@ -18,7 +18,7 @@ namespace TestUtils
{
createEnvironmentOptions = createEnvironmentOptions ?? new CreateEnvironmentOptions();
var userPath = createEnvironmentOptions.UserProfilePath.ToNPath();
var userPath = createEnvironmentOptions.UserProfilePath;
var localAppData = userPath.Parent.Combine("LocalAppData").ToString();
var appData = userPath.Parent.Combine("AppData").ToString();

Просмотреть файл

@ -68,15 +68,15 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GitHub.Api\GitHub.Api.csproj">
<ProjectReference Include="..\..\GitHub.Api\GitHub.Api.csproj">
<Project>{b389adaf-62cc-486e-85b4-2d8b078df763}</Project>
<Name>GitHub.Api</Name>
</ProjectReference>
<ProjectReference Include="..\GitHub.Logging\GitHub.Logging.csproj">
<ProjectReference Include="..\..\GitHub.Logging\GitHub.Logging.csproj">
<Project>{bb6a8eda-15d8-471b-a6ed-ee551e0b3ba0}</Project>
<Name>GitHub.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.csproj">
<ProjectReference Include="..\..\UnityExtension\Assets\Editor\GitHub.Unity\GitHub.Unity.csproj">
<Project>{add7a18b-dd2a-4c22-a2c1-488964eff30a}</Project>
<Name>GitHub.Unity</Name>
</ProjectReference>

Просмотреть файл

Просмотреть файл

@ -17,13 +17,13 @@ namespace UnitTests
public void TestSetup()
{
var fileSystem = SubstituteFactory.CreateFileSystem(new CreateFileSystemOptions());
NPathFileSystemProvider.Current = fileSystem;
NPath.FileSystem = fileSystem;
}
[TearDown]
public void TestTearDown()
{
NPathFileSystemProvider.Current = null;
NPath.FileSystem = null;
}
[Test, Sequential]
@ -34,11 +34,11 @@ namespace UnitTests
[Values(@"test.txt", "UnityProject/test.txt", "test.txt")]string expected)
{
var environment = Substitute.For<IEnvironment>();
environment.RepositoryPath.Returns(repositoryPath);
environment.UnityProjectPath.Returns(projectPath);
environment.RepositoryPath.Returns(repositoryPath.ToNPath());
environment.UnityProjectPath.Returns(projectPath.ToNPath());
NPath nExpected = expected;
var repositoryFilePath = environment.GetRepositoryPath(path);
NPath nExpected = expected.ToNPath();
var repositoryFilePath = environment.GetRepositoryPath(path.ToNPath());
repositoryFilePath.Should().Be(nExpected);
}
@ -50,10 +50,10 @@ namespace UnitTests
[Values(@"test.txt")]string path)
{
var environment = Substitute.For<IEnvironment>();
environment.RepositoryPath.Returns(repositoryPath);
environment.UnityProjectPath.Returns(projectPath);
environment.RepositoryPath.Returns(repositoryPath.ToNPath());
environment.UnityProjectPath.Returns(projectPath.ToNPath());
var repositoryFilePath = environment.GetRepositoryPath(path);
var repositoryFilePath = environment.GetRepositoryPath(path.ToNPath());
}
[Test, Sequential]
@ -64,11 +64,11 @@ namespace UnitTests
[Values("test.txt", "Assets/test.txt", "test.txt")] string expected)
{
var environment = Substitute.For<IEnvironment>();
environment.RepositoryPath.Returns(repositoryPath);
environment.UnityProjectPath.Returns(projectPath);
environment.RepositoryPath.Returns(repositoryPath.ToNPath());
environment.UnityProjectPath.Returns(projectPath.ToNPath());
NPath nExpected = expected;
var repositoryFilePath = environment.GetAssetPath(path);
NPath nExpected = expected.ToNPath();
var repositoryFilePath = environment.GetAssetPath(path.ToNPath());
repositoryFilePath.Should().Be(nExpected);
}
@ -80,10 +80,10 @@ namespace UnitTests
[Values("test.txt")] string path)
{
var environment = Substitute.For<IEnvironment>();
environment.RepositoryPath.Returns(repositoryPath);
environment.UnityProjectPath.Returns(projectPath);
environment.RepositoryPath.Returns(repositoryPath.ToNPath());
environment.UnityProjectPath.Returns(projectPath.ToNPath());
var repositoryFilePath = environment.GetAssetPath(path);
var repositoryFilePath = environment.GetAssetPath(path.ToNPath());
}
}
}

Просмотреть файл

@ -8,18 +8,6 @@ namespace UnitTests
[TestFixture]
public class BranchListOutputProcessorTests
{
//[Test]
public void IntegrationTest_MonoRepo()
{
var filesystem = new FileSystem();
var environment = new DefaultEnvironment();
environment.UnityProjectPath = @"D:\code\github\UnityInternal\src\UnityExtension";
var gitEnvironment = new WindowsGitEnvironment(environment, filesystem);
var fact = new GitObjectFactory(environment);
var pm = new ProcessManager(environment, gitEnvironment);
var results = pm.GetGitBranches(@"D:\code\github\UnityInternal");
}
[Test]
public void ShouldProcessOutput()
{

Просмотреть файл

@ -17,15 +17,13 @@ namespace UnitTests
var substituteFactory = new TestUtils.SubstituteFactory();
var fileSystem = substituteFactory.CreateFileSystem(new CreateFileSystemOptions() { });
NPathFileSystemProvider.Current.Should().BeNull();
NPathFileSystemProvider.Current = fileSystem;
NPath.FileSystem = fileSystem;
}
[TestFixtureTearDown]
public void TestFixtureTearDown()
{
NPathFileSystemProvider.Current.Should().NotBeNull();
NPathFileSystemProvider.Current = null;
NPath.FileSystem = null;
}
[Test]
@ -37,7 +35,7 @@ namespace UnitTests
item.ShouldThrow<InvalidOperationException>();
item = () => { FileSystemHelpers.FindCommonPath(new List<string> { null }); };
item.ShouldThrow<ArgumentNullException>();
item.ShouldThrow<InvalidOperationException>();
item = () => { FileSystemHelpers.FindCommonPath(new List<string> { "" }); };
item.ShouldThrow<InvalidOperationException>();

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше