aspnetcore/src/Tools/dotnet-watch/test/Scenario/WatchableApp.cs

129 lines
4.3 KiB
C#

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Microsoft.Extensions.CommandLineUtils;
using Xunit.Abstractions;
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
{
public class WatchableApp : IDisposable
{
private static readonly TimeSpan DefaultMessageTimeOut = TimeSpan.FromSeconds(30);
private const string StartedMessage = "Started";
private const string ExitingMessage = "Exiting";
private const string WatchExitedMessage = "watch : Exited";
private const string WaitingForFileChangeMessage = "watch : Waiting for a file to change";
private readonly ITestOutputHelper _logger;
private string _appName;
private bool _prepared;
public WatchableApp(string appName, ITestOutputHelper logger)
{
_logger = logger;
_appName = appName;
Scenario = new ProjectToolScenario(logger);
Scenario.AddTestProjectFolder(appName);
SourceDirectory = Path.Combine(Scenario.WorkFolder, appName);
}
public ProjectToolScenario Scenario { get; }
public AwaitableProcess Process { get; protected set; }
public string SourceDirectory { get; }
public Task HasRestarted()
=> Process.GetOutputLineAsync(StartedMessage, DefaultMessageTimeOut);
public async Task HasExited()
{
await Process.GetOutputLineAsync(ExitingMessage, DefaultMessageTimeOut);
await Process.GetOutputLineStartsWithAsync(WatchExitedMessage, DefaultMessageTimeOut);
}
public async Task IsWaitingForFileChange()
{
await Process.GetOutputLineStartsWithAsync(WaitingForFileChangeMessage, DefaultMessageTimeOut);
}
public bool UsePollingWatcher { get; set; }
public async Task<int> GetProcessId()
{
var line = await Process.GetOutputLineStartsWithAsync("PID =", DefaultMessageTimeOut);
var pid = line.Split('=').Last();
return int.Parse(pid);
}
public async Task PrepareAsync()
{
await Scenario.RestoreAsync(_appName);
await Scenario.BuildAsync(_appName);
_prepared = true;
}
public void Start(IEnumerable<string> arguments, [CallerMemberName] string name = null)
{
if (!_prepared)
{
throw new InvalidOperationException($"Call {nameof(PrepareAsync)} first");
}
var args = new List<string>
{
Scenario.DotNetWatchPath,
};
args.AddRange(arguments);
var spec = new ProcessSpec
{
Executable = DotNetMuxer.MuxerPathOrDefault(),
Arguments = args,
WorkingDirectory = SourceDirectory,
EnvironmentVariables =
{
["DOTNET_CLI_CONTEXT_VERBOSE"] = bool.TrueString,
["DOTNET_USE_POLLING_FILE_WATCHER"] = UsePollingWatcher.ToString(),
},
};
Process = new AwaitableProcess(spec, _logger);
Process.Start();
}
public Task StartWatcherAsync([CallerMemberName] string name = null)
=> StartWatcherAsync(Array.Empty<string>(), name);
public async Task StartWatcherAsync(string[] arguments, [CallerMemberName] string name = null)
{
if (!_prepared)
{
await PrepareAsync();
}
var args = new[] { "run", "--" }.Concat(arguments);
Start(args, name);
// Make this timeout long because it depends much on the MSBuild compilation speed.
// Slow machines may take a bit to compile and boot test apps
await Process.GetOutputLineAsync(StartedMessage, TimeSpan.FromMinutes(2));
}
public virtual void Dispose()
{
_logger?.WriteLine("Disposing WatchableApp");
Process?.Dispose();
Scenario?.Dispose();
}
}
}