Make the collection of logs (the `Logs` class) more capable by making it
disposable (which will dispose all contained logs) and give it a Directory
property to state where the logs should be stored. This makes it possible to
simplify a few repeated path calculations. It also allows us to easily dispose
the entire collection of logs when done with a test, as opposed to dispoing
the logs one by one.

Make LogFile more capable:

* Add support for writing byte arrays.
* Add support for logging after disposal: this will still write to the file,
  and not keep any files open after finished writing. This fixes a problem
  where writing to a log after it was disposed would crash xharness (which is
  not all that uncommon, given the async nature of how xharness runs tests).

This makes it possible to get rid of LogStream, and use LogFile instead.
This commit is contained in:
Rolf Bjarne Kvinge 2017-11-28 14:27:31 +01:00 коммит произвёл GitHub
Родитель ea20f841f8
Коммит f9b4c93cf0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 200 добавлений и 159 удалений

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

@ -94,9 +94,14 @@ namespace xharness
set { log_directory = value; }
}
Log main_log;
public Logs Logs = new Logs ();
Logs logs;
public Logs Logs {
get {
return logs ?? (logs = new Logs (LogDirectory));
}
}
Log main_log;
public Log MainLog {
get { return main_log; }
set { main_log = value; }
@ -123,7 +128,7 @@ namespace xharness
var sims = new Simulators () {
Harness = Harness,
};
await sims.LoadAsync (Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator list"));
await sims.LoadAsync (Logs.Create ("simulator-list.log", "Simulator list"));
simulators = await sims.FindAsync (Target, main_log);
return simulators != null;
@ -336,7 +341,7 @@ namespace xharness
ensure_clean_simulator_state = value;
}
}
public bool TestsSucceeded (LogStream listener_log, bool timed_out, bool crashed)
public bool TestsSucceeded (Log listener_log, bool timed_out, bool crashed)
{
string log;
using (var reader = listener_log.GetReader ())
@ -372,9 +377,9 @@ namespace xharness
mainResultNode.Attributes ["name"].Value = Target.AsString ();
// store a clean version of the logs, later this will be used by the bots to show results in github/web
var path = listener_log.FullPath;
path = path.Replace (".log", ".xml");
path = Path.ChangeExtension (path, "xml");
testsResults.Save (path);
Logs.Add (new LogFile ("Test xml", path));
Logs.AddFile (path, "Test xml");
}
} catch (Exception e) {
main_log.WriteLine ("Could not parse xml result file: {0}", e);
@ -433,8 +438,8 @@ namespace xharness
public async Task<int> RunAsync ()
{
CrashReportSnapshot crash_reports;
LogStream device_system_log = null;
LogStream listener_log = null;
Log device_system_log = null;
Log listener_log = null;
Log run_log = main_log;
Initialize ();
@ -498,7 +503,7 @@ namespace xharness
args.AppendFormat (" -argument=-app-arg:-transport:{0}", transport);
args.AppendFormat (" -setenv=NUNIT_TRANSPORT={0}", transport);
listener_log = Logs.CreateStream (LogDirectory, string.Format ("test-{0}-{1:yyyyMMdd_HHmmss}.log", mode, DateTime.Now), "Test log");
listener_log = Logs.Create ($"test-{mode}-{Harness.Timestamp}.log", "Test log");
SimpleListener listener;
switch (transport) {
@ -578,10 +583,10 @@ namespace xharness
args.Append (" --stdout=").Append (StringUtils.Quote (stderr_tty));
args.Append (" --stderr=").Append (StringUtils.Quote (stderr_tty));
} else {
var stdout_log = Logs.CreateFile ("Standard output", Path.Combine (LogDirectory, "stdout.log"));
var stderr_log = Logs.CreateFile ("Standard error", Path.Combine (LogDirectory, "stderr.log"));
args.Append (" --stdout=").Append (StringUtils.Quote (stdout_log.FullPath));
args.Append (" --stderr=").Append (StringUtils.Quote (stderr_log.FullPath));
var stdout_log = Logs.CreateFile ($"stdout-{Harness.Timestamp}.log", "Standard output");
var stderr_log = Logs.CreateFile ($"stderr-{Harness.Timestamp}.log", "Standard error");
args.Append (" --stdout=").Append (StringUtils.Quote (stdout_log));
args.Append (" --stderr=").Append (StringUtils.Quote (stderr_log));
}
}
@ -591,7 +596,7 @@ namespace xharness
main_log.WriteLine ("System log for the '{1}' simulator is: {0}", sim.SystemLog, sim.Name);
bool isCompanion = sim != simulator;
var log = new CaptureLog (sim.SystemLog, entire_file: Harness.Action != HarnessAction.Jenkins)
var log = new CaptureLog (Logs, sim.SystemLog, entire_file: Harness.Action != HarnessAction.Jenkins)
{
Path = Path.Combine (LogDirectory, sim.Name + ".log"),
Description = isCompanion ? "System log (companion)" : "System log",
@ -678,7 +683,7 @@ namespace xharness
AddDeviceName (args);
device_system_log = Logs.CreateStream (LogDirectory, $"device-{device_name}-{DateTime.Now:yyyyMMdd_HHmmss}.log", "Device log");
device_system_log = Logs.Create ($"device-{device_name}-{Harness.Timestamp}.log", "Device log");
var logdev = new DeviceLogCapturer () {
Harness = Harness,
Log = device_system_log,

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

@ -29,6 +29,12 @@ namespace xharness
public bool UseSystem { get; set; } // if the system XI/XM should be used, or the locally build XI/XM.
public HashSet<string> Labels { get; } = new HashSet<string> ();
public static string Timestamp {
get {
return $"{DateTime.Now:yyyyMMdd_HHmmss}";
}
}
// This is the maccore/tests directory.
string root_directory;
public string RootDirectory {
@ -761,12 +767,12 @@ namespace xharness
return ProcessHelper.ExecuteCommandAsync (Path.Combine (XcodeRoot, "Contents", "Developer", "usr", "bin", executable), args, log, timeout: timeout);
}
public async Task ShowSimulatorList (LogStream log)
public async Task ShowSimulatorList (Log log)
{
await ExecuteXcodeCommandAsync ("simctl", "list", log, TimeSpan.FromSeconds (10));
}
public async Task<LogFile> SymbolicateCrashReportAsync (Log log, LogFile report)
public async Task<LogFile> SymbolicateCrashReportAsync (Logs logs, Log log, LogFile report)
{
var symbolicatecrash = Path.Combine (XcodeRoot, "Contents/SharedFrameworks/DTDeviceKitBase.framework/Versions/A/Resources/symbolicatecrash");
if (!File.Exists (symbolicatecrash))
@ -777,7 +783,8 @@ namespace xharness
return report;
}
var symbolicated = new LogFile ("Symbolicated crash report", Path.ChangeExtension (report.Path, ".symbolicated.log"));
var name = Path.GetFileName (report.Path);
var symbolicated = logs.Create (Path.ChangeExtension (name, ".symbolicated.log"), $"Symbolicated crash report: {name}");
var environment = new Dictionary<string, string> { { "DEVELOPER_DIR", Path.Combine (XcodeRoot, "Contents", "Developer") } };
var rv = await ProcessHelper.ExecuteCommandAsync (symbolicatecrash, StringUtils.Quote (report.Path), symbolicated, TimeSpan.FromMinutes (1), environment);
if (rv.Succeeded) {;
@ -851,16 +858,15 @@ namespace xharness
if (!Device) {
crash_reports = new List<LogFile> (end_crashes.Count);
foreach (var path in end_crashes) {
var logPath = Path.Combine (LogDirectory, Path.GetFileName (path));
File.Copy (path, logPath, true);
crash_reports.Add (Logs.CreateFile ("Crash report: " + Path.GetFileName (path), logPath));
Logs.AddFile (path, $"Crash report: {Path.GetFileName (path)}");
}
} else {
// Download crash reports from the device. We put them in the project directory so that they're automatically deleted on wrench
// (if we put them in /tmp, they'd never be deleted).
var downloaded_crash_reports = new List<LogFile> ();
foreach (var file in end_crashes) {
var crash_report_target = Logs.CreateFile ("Crash report: " + Path.GetFileName (file), Path.Combine (LogDirectory, Path.GetFileName (file)));
var name = Path.GetFileName (file);
var crash_report_target = Logs.Create (name, $"Crash report: {name}");
var sb = new StringBuilder ();
sb.Append (" --download-crash-report=").Append (StringUtils.Quote (file));
sb.Append (" --download-crash-report-to=").Append (StringUtils.Quote (crash_report_target.Path));
@ -870,8 +876,7 @@ namespace xharness
var result = await ProcessHelper.ExecuteCommandAsync (Harness.MlaunchPath, sb.ToString (), Log, TimeSpan.FromMinutes (1));
if (result.Succeeded) {
Log.WriteLine ("Downloaded crash report {0} to {1}", file, crash_report_target.Path);
crash_report_target = await Harness.SymbolicateCrashReportAsync (Log, crash_report_target);
Logs.Add (crash_report_target);
crash_report_target = await Harness.SymbolicateCrashReportAsync (Logs, Log, crash_report_target);
downloaded_crash_reports.Add (crash_report_target);
} else {
Log.WriteLine ("Could not download crash report {0}", file);

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

@ -33,7 +33,6 @@ namespace xharness
public bool IncludeSystemPermissionTests = true; // tests that require access to system resources (system contacts, photo library, etc) in order to work
public bool IncludeXtro;
public Logs Logs = new Logs ();
public Log MainLog;
public Log SimulatorLoadLog;
public Log DeviceLoadLog;
@ -43,6 +42,14 @@ namespace xharness
return Path.Combine (Harness.JENKINS_RESULTS_DIRECTORY, "tests");
}
}
Logs logs;
public Logs Logs {
get {
return logs ?? (logs = new Logs (LogDirectory));
}
}
public Simulators Simulators = new Simulators ();
public Devices Devices = new Devices ();
@ -72,7 +79,7 @@ namespace xharness
Devices.Harness = Harness;
if (SimulatorLoadLog == null)
SimulatorLoadLog = Logs.CreateStream (LogDirectory, "simulator-list.log", "Simulator Listing");
SimulatorLoadLog = Logs.Create ("simulator-list.log", "Simulator Listing");
var simulatorLoadTask = Task.Run (async () => {
try {
@ -84,7 +91,7 @@ namespace xharness
});
if (DeviceLoadLog == null)
DeviceLoadLog = Logs.CreateStream (LogDirectory, "device-list.log", "Device Listing");
DeviceLoadLog = Logs.Create ("device-list.log", "Device Listing");
var deviceLoadTask = Task.Run (async () => {
try {
await Devices.LoadAsync (DeviceLoadLog, removed_locked: true);
@ -716,7 +723,7 @@ namespace xharness
{
try {
Directory.CreateDirectory (LogDirectory);
Log log = Logs.CreateStream (LogDirectory, "Harness.log", "Harness log");
Log log = Logs.Create ("Harness.log", "Harness log");
if (Harness.InWrench)
log = Log.CreateAggregatedLog (log, new ConsoleLog ());
Harness.HarnessLog = MainLog = log;
@ -735,7 +742,7 @@ namespace xharness
});
}
if (!string.IsNullOrEmpty (Harness.PeriodicCommand)) {
var periodic_log = Logs.CreateFile ("Periodic command log", Path.Combine (LogDirectory, "PeriodicCommand.log"), false);
var periodic_log = Logs.Create ("PeriodicCommand.log", "Periodic command log");
periodic_log.Timestamp = true;
Task.Run (async () => await ExecutePeriodicCommandAsync (periodic_log));
}
@ -1984,7 +1991,7 @@ function oninitialload ()
protected static string Timestamp {
get {
return $"{DateTime.Now:yyyyMMdd_HHmmss}";
return Harness.Timestamp;
}
}
@ -2036,14 +2043,13 @@ function oninitialload ()
public TestPlatform Platform { get; set; }
public Logs Logs = new Logs ();
public List<Resource> Resources = new List<Resource> ();
Log test_log;
public Log MainLog {
get {
if (test_log == null)
test_log = Logs.CreateStream (LogDirectory, $"main-{Timestamp}.log", "Main log");
test_log = Logs.Create ($"main-{Timestamp}.log", "Main log");
return test_log;
}
}
@ -2062,6 +2068,13 @@ function oninitialload ()
}
}
Logs logs;
public Logs Logs {
get {
return logs ?? (logs = new Logs (LogDirectory));
}
}
Task execute_task;
async Task RunInternalAsync ()
{
@ -2083,13 +2096,13 @@ function oninitialload ()
if ((ExecutionResult & ~TestExecutingResult.StateMask) == 0)
throw new Exception ("Result not set!");
} catch (Exception e) {
using (var log = Logs.CreateStream (LogDirectory, $"execution-failure-{Timestamp}.log", "Execution failure")) {
using (var log = Logs.Create ($"execution-failure-{Timestamp}.log", "Execution failure")) {
ExecutionResult = TestExecutingResult.HarnessException;
FailureMessage = $"Harness exception for '{TestName}': {e}";
log.WriteLine (FailureMessage);
}
} finally {
MainLog.Dispose ();
logs?.Dispose ();
duration.Stop ();
}
@ -2100,7 +2113,7 @@ function oninitialload ()
{
test_log = null;
failure_message = null;
Logs.Clear ();
logs = null;
duration.Reset ();
execution_result = TestExecutingResult.NotStarted;
execute_task = null;
@ -2181,9 +2194,7 @@ function oninitialload ()
switch (name) {
case "AddFile":
var src = cmd.Substring (name.Length + 1).Trim ();
var tgt = Path.Combine (LogDirectory, Path.GetFileName (src));
File.Copy (src, tgt, true);
Logs.CreateFile (Path.GetFileName (tgt), tgt);
Logs.AddFile (src);
break;
default:
Harness.HarnessLog.WriteLine ("Unknown @MonkeyWrench command in {0}: {1}", TestName, name);
@ -2337,7 +2348,7 @@ function oninitialload ()
{
ExecutionResult = TestExecutingResult.Building;
using (var resource = await NotifyAndAcquireDesktopResourceAsync ()) {
var log = Logs.CreateStream (LogDirectory, $"build-{Platform}-{Timestamp}.txt", "Build log");
var log = Logs.Create ($"build-{Platform}-{Timestamp}.txt", "Build log");
await RestoreNugetsAsync (log, resource);
using (var xbuild = new Process ()) {
xbuild.StartInfo.FileName = "/Applications/Visual Studio.app/Contents/MacOS/vstool";
@ -2381,7 +2392,7 @@ function oninitialload ()
make.StartInfo.WorkingDirectory = WorkingDirectory;
make.StartInfo.Arguments = Target;
SetEnvironmentVariables (make);
var log = Logs.CreateStream (LogDirectory, $"make-{Platform}-{Timestamp}.txt", "Build log");
var log = Logs.Create ($"make-{Platform}-{Timestamp}.txt", "Build log");
log.Timestamp = true;
LogProcessExecution (log, make, "Making {0} in {1}", Target, WorkingDirectory);
if (!Harness.DryRun) {
@ -2411,7 +2422,7 @@ function oninitialload ()
protected override async Task ExecuteAsync ()
{
using (var resource = await NotifyAndAcquireDesktopResourceAsync ()) {
var log = Logs.CreateStream (LogDirectory, $"build-{Platform}-{Timestamp}.txt", "Build log");
var log = Logs.Create ($"build-{Platform}-{Timestamp}.txt", "Build log");
await RestoreNugetsAsync (log, resource);
@ -2473,7 +2484,7 @@ function oninitialload ()
public async override Task CleanAsync ()
{
var log = Logs.CreateStream (LogDirectory, $"clean-{Platform}-{Timestamp}.txt", "Clean log");
var log = Logs.Create ($"clean-{Platform}-{Timestamp}.txt", "Clean log");
await CleanProjectAsync (log, ProjectFile, SpecifyPlatform ? ProjectPlatform : null, SpecifyConfiguration ? ProjectConfiguration : null);
// Iterate over all the project references as well.
@ -2522,8 +2533,8 @@ function oninitialload ()
protected override async Task RunTestAsync ()
{
using (var resource = await NotifyAndAcquireDesktopResourceAsync ()) {
var xmlLog = Logs.CreateFile ("XML log", Path.Combine (LogDirectory, "log.xml"));
var log = Logs.CreateStream (LogDirectory, $"execute-{Timestamp}.txt", "Execution log");
var xmlLog = Logs.CreateFile ($"log-{Timestamp}.xml", "XML log");
var log = Logs.Create ($"execute-{Timestamp}.txt", "Execution log");
log.Timestamp = true;
using (var proc = new Process ()) {
@ -2533,10 +2544,10 @@ function oninitialload ()
args.Append (StringUtils.Quote (Path.GetFullPath (TestExecutable))).Append (' ');
args.Append (StringUtils.Quote (Path.GetFullPath (TestLibrary))).Append (' ');
if (IsNUnit3) {
args.Append ("-result=").Append (StringUtils.Quote (xmlLog.FullPath)).Append (";format=nunit2 ");
args.Append ("-result=").Append (StringUtils.Quote (xmlLog)).Append (";format=nunit2 ");
args.Append ("--labels=All ");
} else {
args.Append ("-xml=" + StringUtils.Quote (xmlLog.FullPath)).Append (' ');
args.Append ("-xml=" + StringUtils.Quote (xmlLog)).Append (' ');
args.Append ("-labels ");
}
proc.StartInfo.Arguments = args.ToString ();
@ -2561,18 +2572,16 @@ function oninitialload ()
if (ProduceHtmlReport) {
try {
var output = Logs.CreateStream (LogDirectory, $"Log-{Timestamp}.html", "HTML log");
var output = Logs.Create ($"Log-{Timestamp}.html", "HTML log");
using (var srt = new StringReader (File.ReadAllText (Path.Combine (Harness.RootDirectory, "HtmlTransform.xslt")))) {
using (var sri = xmlLog.GetReader ()) {
using (var sri = File.OpenRead (xmlLog)) {
using (var xrt = System.Xml.XmlReader.Create (srt)) {
using (var xri = System.Xml.XmlReader.Create (sri)) {
var xslt = new System.Xml.Xsl.XslCompiledTransform ();
xslt.Load (xrt);
using (var sw = output.GetWriter ()) {
using (var xwo = System.Xml.XmlWriter.Create (sw, xslt.OutputSettings)) // use OutputSettings of xsl, so it can be output as HTML
{
xslt.Transform (xri, xwo);
}
using (var xwo = System.Xml.XmlWriter.Create (output, xslt.OutputSettings)) // use OutputSettings of xsl, so it can be output as HTML
{
xslt.Transform (xri, xwo);
}
}
}
@ -2696,11 +2705,11 @@ function oninitialload ()
using (var proc = new Process ()) {
proc.StartInfo.FileName = Path;
if (IsUnitTest) {
using (var xml = Logs.CreateFile ("NUnit results", System.IO.Path.Combine (LogDirectory, $"test-{Platform}-{Timestamp}.xml"), false))
proc.StartInfo.Arguments = $"-result={StringUtils.Quote (xml.FullPath)}";
var xml = Logs.CreateFile ($"test-{Platform}-{Timestamp}.xml", "NUnit results");
proc.StartInfo.Arguments = $"-result={StringUtils.Quote (xml)}";
}
Jenkins.MainLog.WriteLine ("Executing {0} ({1})", TestName, Mode);
var log = Logs.CreateStream (LogDirectory, $"execute-{Platform}-{Timestamp}.txt", "Execution log");
var log = Logs.Create ($"execute-{Platform}-{Timestamp}.txt", "Execution log");
log.WriteLine ("{0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments);
if (!Harness.DryRun) {
ExecutionResult = TestExecutingResult.Running;
@ -2958,9 +2967,9 @@ function oninitialload ()
{
Jenkins.MainLog.WriteLine ("Running '{0}' on device (candidates: '{1}')", ProjectFile, string.Join ("', '", Candidates.Select ((v) => v.Name).ToArray ()));
var install_log = Logs.CreateStream (LogDirectory, $"install-{Timestamp}.log", "Install log");
var install_log = Logs.Create ($"install-{Timestamp}.log", "Install log");
install_log.Timestamp = true;
var uninstall_log = Logs.CreateStream (LogDirectory, $"uninstall-{Timestamp}.log", "Uninstall log");
var uninstall_log = Logs.Create ($"uninstall-{Timestamp}.log", "Uninstall log");
using (var device_resource = await NotifyBlockingWaitAsync (Jenkins.GetDeviceResources (Candidates).AcquireAnyConcurrentAsync ())) {
try {
// Set the device we acquired.
@ -3007,7 +3016,7 @@ function oninitialload ()
if (!Failed) {
// Run the app
runner.MainLog = Logs.CreateStream (LogDirectory, $"run-{Device.UDID}-{Timestamp}.log", "Run log");
runner.MainLog = Logs.Create ($"run-{Device.UDID}-{Timestamp}.log", "Run log");
await runner.RunAsync ();
if (!string.IsNullOrEmpty (runner.FailureMessage))
@ -3027,7 +3036,7 @@ function oninitialload ()
ProjectFile = TestProject.GetTodayExtension ().Path,
Target = AppRunnerTarget,
LogDirectory = LogDirectory,
MainLog = Logs.CreateStream (LogDirectory, $"extension-run-{Device.UDID}-{Timestamp}.log", "Extension run log"),
MainLog = Logs.Create ($"extension-run-{Device.UDID}-{Timestamp}.log", "Extension run log"),
DeviceName = Device.Name,
CompanionDeviceName = CompanionDevice?.Name,
Configuration = ProjectConfiguration,
@ -3055,9 +3064,6 @@ function oninitialload ()
// Also clean up after us locally.
if (Harness.InJenkins || Harness.InWrench || Succeeded)
await BuildTask.CleanAsync ();
uninstall_log.Dispose ();
install_log.Dispose ();
}
}
}
@ -3120,7 +3126,7 @@ function oninitialload ()
EnsureCleanSimulatorState = clean_state,
Target = AppRunnerTarget,
LogDirectory = LogDirectory,
MainLog = Logs.CreateStream (LogDirectory, $"run-{Device.UDID}-{Timestamp}.log", "Run log"),
MainLog = Logs.Create ($"run-{Device.UDID}-{Timestamp}.log", "Run log"),
IncludeSystemPermissionTests = Jenkins.IncludeSystemPermissionTests,
};
runner.Simulators = Simulators;

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

@ -8,28 +8,39 @@ namespace xharness
{
public abstract class Log : TextWriter
{
public Logs Logs { get; private set; }
public string Description;
public bool Timestamp;
protected Log ()
protected Log (Logs logs)
{
Logs = logs;
}
protected Log (string description)
protected Log (Logs logs, string description)
{
Logs = logs;
Description = description;
}
public abstract string FullPath { get; }
protected abstract void WriteImpl (string value);
protected virtual void WriteImpl (string value)
{
Write (Encoding.UTF8.GetBytes (value));
}
public virtual StreamReader GetReader ()
public virtual void Write (byte [] buffer)
{
Write (buffer, 0, buffer.Length);
}
public virtual void Write (byte[] buffer, int offset, int count)
{
throw new NotSupportedException ();
}
public virtual StreamWriter GetWriter ()
public virtual StreamReader GetReader ()
{
throw new NotSupportedException ();
}
@ -77,6 +88,7 @@ namespace xharness
Log [] logs;
public AggregatedLog (params Log [] logs)
: base (null)
{
this.logs = logs;
}
@ -88,28 +100,52 @@ namespace xharness
foreach (var log in logs)
log.WriteImpl (value);
}
public override void Write (byte [] buffer, int offset, int count)
{
foreach (var log in logs)
log.Write (buffer, offset, count);
}
}
}
public class LogFile : Log
{
object lock_obj = new object ();
bool append;
public string Path;
StreamWriter writer;
public string Path { get; private set; }
FileStream writer;
bool disposed;
public LogFile (string description, string path, bool append = true)
: base (description)
public LogFile (Logs logs, string description, string path, bool append = true)
: base (logs, description)
{
Path = path;
this.append = append;
if (!append)
File.WriteAllText (path, string.Empty);
}
protected override void WriteImpl (string value)
public override void Write (byte [] buffer, int offset, int count)
{
GetWriter ().Write (value);
try {
// We don't want to open the file every time someone writes to the log, so we keep it as an instance
// variable until we're disposed. Due to the async nature of how we run tests, writes may still
// happen after we're disposed, in which case we create a temporary stream we close after writing
lock (lock_obj) {
var fs = writer;
if (fs == null) {
fs = new FileStream (Path, FileMode.Append, FileAccess.Write, FileShare.Read);
}
fs.Write (buffer, offset, count);
if (disposed) {
fs.Dispose ();
} else {
writer = fs;
}
}
} catch (Exception e) {
Console.WriteLine ($"Failed to write to the file {Path}: {e.Message}.");
return;
}
}
public override string FullPath {
@ -123,59 +159,6 @@ namespace xharness
return new StreamReader (new FileStream (Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
}
public override StreamWriter GetWriter ()
{
lock (lock_obj) {
if (writer == null) {
writer = new StreamWriter (new FileStream (Path, append ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.Read));
writer.AutoFlush = true;
}
}
return writer;
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
if (writer != null) {
writer.Dispose ();
writer = null;
}
}
}
public class LogStream : Log
{
string path;
FileStream fs;
StreamWriter writer;
public override StreamReader GetReader ()
{
return new StreamReader (new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
}
public override StreamWriter GetWriter ()
{
return writer ?? (writer = new StreamWriter (fs) { AutoFlush = true });
}
public LogStream (string description, string path)
: base (description)
{
this.path = path;
fs = new FileStream (path, FileMode.Create, FileAccess.Write, FileShare.Read);
}
protected override void WriteImpl (string value)
{
var w = GetWriter ();
w.Write (value);
w.Flush ();
}
protected override void Dispose (bool disposing)
{
base.Dispose (disposing);
@ -185,41 +168,83 @@ namespace xharness
writer = null;
}
if (fs != null) {
fs.Dispose ();
fs = null;
}
}
public override string FullPath {
get {
return path;
}
disposed = true;
}
}
public class Logs : List<Log>
public class Logs : List<Log>, IDisposable
{
public LogStream CreateStream (string directory, string filename, string name)
public string Directory;
public Logs (string directory)
{
Directory.CreateDirectory (directory);
var rv = new LogStream (name, Path.GetFullPath (Path.Combine (directory, filename)));
Directory = directory;
}
// Create a new log backed with a file
public LogFile Create (string filename, string name)
{
return Create (Directory, filename, name);
}
LogFile Create (string directory, string filename, string name)
{
System.IO.Directory.CreateDirectory (directory);
var rv = new LogFile (this, name, Path.GetFullPath (Path.Combine (directory, filename)));
Add (rv);
return rv;
}
public LogFile CreateFile (string description, string path, bool append = true)
// Adds an existing file to this collection of logs.
// If the file is not inside the log directory, then it's copied there.
// 'path' must be a full path to the file.
public LogFile AddFile (string path)
{
var rv = new LogFile (description, path, append);
Add (rv);
return rv;
return AddFile (path, Path.GetFileName (path));
}
// Adds an existing file to this collection of logs.
// If the file is not inside the log directory, then it's copied there.
// 'path' must be a full path to the file.
public LogFile AddFile (string path, string name)
{
if (!path.StartsWith (Directory, StringComparison.Ordinal)) {
var newPath = Path.Combine (Directory, Path.GetFileNameWithoutExtension (path) + "-" + Harness.Timestamp + Path.GetExtension (path));
File.Copy (path, newPath, true);
path = newPath;
}
var log = new LogFile (this, name, path, true);
Add (log);
return log;
}
// Create an empty file in the log directory and return the full path to the file
public string CreateFile (string path, string description)
{
using (var rv = new LogFile (this, description, Path.Combine (Directory, path), false)) {
Add (rv);
return rv.FullPath;
}
}
public void Dispose ()
{
foreach (var log in this)
log.Dispose ();
}
}
// A log that writes to standard output
public class ConsoleLog : Log
{
StringBuilder captured = new StringBuilder ();
public ConsoleLog ()
: base (null)
{
}
protected override void WriteImpl (string value)
{
captured.Append (value);
@ -232,11 +257,6 @@ namespace xharness
}
}
public override StreamWriter GetWriter ()
{
return new StreamWriter (Console.OpenStandardOutput ());
}
public override StreamReader GetReader ()
{
var str = new MemoryStream (System.Text.Encoding.UTF8.GetBytes (captured.ToString ()));
@ -244,6 +264,8 @@ namespace xharness
}
}
// A log that captures data written to a separate file between two moments in time
// (between StartCapture and StopCapture).
public class CaptureLog : Log
{
public string CapturePath { get; private set; }
@ -253,7 +275,8 @@ namespace xharness
long endPosition;
bool entire_file;
public CaptureLog (string capture_path, bool entire_file = false)
public CaptureLog (Logs logs, string capture_path, bool entire_file = false)
: base (logs)
{
CapturePath = capture_path;
this.entire_file = entire_file;
@ -349,11 +372,13 @@ namespace xharness
}
}
// A log that forwards all written data to a callback
public class CallbackLog : Log
{
public Action<string> OnWrite;
public CallbackLog (Action<string> onWrite)
: base (null)
{
OnWrite = onWrite;
}

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

@ -9,7 +9,7 @@ namespace xharness
{
public abstract class SimpleListener : IDisposable
{
StreamWriter output_writer;
Log output_writer;
string xml_data;
TaskCompletionSource<bool> stopped = new TaskCompletionSource<bool> ();
@ -18,7 +18,7 @@ namespace xharness
public IPAddress Address { get; set; }
public int Port { get; set; }
public Log Log { get; set; }
public LogStream TestLog { get; set; }
public Log TestLog { get; set; }
public bool AutoExit { get; set; }
public bool XmlOutput { get; set; }
@ -26,7 +26,7 @@ namespace xharness
protected abstract void Start ();
protected abstract void Stop ();
public StreamWriter OutputWriter {
public Log OutputWriter {
get {
return output_writer;
}
@ -38,7 +38,7 @@ namespace xharness
connected.Set ();
if (output_writer == null) {
output_writer = TestLog.GetWriter ();
output_writer = TestLog;
// a few extra bits of data only available from this side
var local_data =
$@"[Local Date/Time: {DateTime.Now}]

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

@ -57,7 +57,7 @@ namespace xharness
int i;
int total = 0;
NetworkStream stream = client.GetStream ();
var fs = OutputWriter.BaseStream;
var fs = OutputWriter;
while ((i = stream.Read (buffer, 0, buffer.Length)) != 0) {
fs.Write (buffer, 0, i);
fs.Flush ();