259 строки
6.1 KiB
C#
259 строки
6.1 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics.Contracts;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Xharness.Logging;
|
|
|
|
namespace Xharness
|
|
{
|
|
|
|
public class LogFile : Log, ILogFile
|
|
{
|
|
object lock_obj = new object ();
|
|
public string Path { get; private set; }
|
|
FileStream writer;
|
|
bool disposed;
|
|
|
|
public LogFile (ILogs logs, string description, string path, bool append = true)
|
|
: base (logs, description)
|
|
{
|
|
Path = path;
|
|
if (!append)
|
|
File.WriteAllText (path, string.Empty);
|
|
}
|
|
|
|
public override void Write (byte [] buffer, int offset, int count)
|
|
{
|
|
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 void Flush()
|
|
{
|
|
base.Flush();
|
|
|
|
if (writer != null && !disposed)
|
|
writer.Flush ();
|
|
}
|
|
|
|
public override string FullPath {
|
|
get {
|
|
return Path;
|
|
}
|
|
}
|
|
|
|
public override StreamReader GetReader ()
|
|
{
|
|
return new StreamReader (new FileStream (Path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
|
}
|
|
|
|
protected override void Dispose (bool disposing)
|
|
{
|
|
base.Dispose (disposing);
|
|
|
|
if (writer != null) {
|
|
writer.Dispose ();
|
|
writer = null;
|
|
}
|
|
|
|
disposed = true;
|
|
}
|
|
|
|
}
|
|
|
|
// A log that writes to standard output
|
|
public class ConsoleLog : Log
|
|
{
|
|
StringBuilder captured = new StringBuilder ();
|
|
|
|
public ConsoleLog ()
|
|
: base (null)
|
|
{
|
|
}
|
|
|
|
public override void WriteImpl (string value)
|
|
{
|
|
captured.Append (value);
|
|
Console.Write (value);
|
|
}
|
|
|
|
public override string FullPath {
|
|
get {
|
|
throw new NotSupportedException ();
|
|
}
|
|
}
|
|
|
|
public override StreamReader GetReader ()
|
|
{
|
|
var str = new MemoryStream (System.Text.Encoding.UTF8.GetBytes (captured.ToString ()));
|
|
return new StreamReader (str, System.Text.Encoding.UTF8, false);
|
|
}
|
|
}
|
|
|
|
// 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; }
|
|
public string Path { get; set; }
|
|
|
|
long startPosition;
|
|
long endPosition;
|
|
bool entire_file;
|
|
|
|
public CaptureLog (ILogs logs, string capture_path, bool entire_file = false)
|
|
: base (logs)
|
|
{
|
|
CapturePath = capture_path;
|
|
this.entire_file = entire_file;
|
|
}
|
|
|
|
public void StartCapture ()
|
|
{
|
|
if (entire_file)
|
|
return;
|
|
|
|
if (File.Exists (CapturePath))
|
|
startPosition = new FileInfo (CapturePath).Length;
|
|
}
|
|
|
|
public void StopCapture ()
|
|
{
|
|
if (!File.Exists (CapturePath)) {
|
|
File.WriteAllText (Path, $"Could not capture the file '{CapturePath}' because it doesn't exist.");
|
|
return;
|
|
}
|
|
|
|
if (entire_file) {
|
|
File.Copy (CapturePath, Path, true);
|
|
return;
|
|
}
|
|
|
|
endPosition = new FileInfo (CapturePath).Length;
|
|
|
|
Capture ();
|
|
}
|
|
|
|
void Capture ()
|
|
{
|
|
if (startPosition == 0 || entire_file)
|
|
return;
|
|
|
|
if (!File.Exists (CapturePath)) {
|
|
File.WriteAllText (Path, $"Could not capture the file '{CapturePath}' because it does not exist.");
|
|
return;
|
|
}
|
|
|
|
var currentEndPosition = endPosition;
|
|
if (currentEndPosition == 0)
|
|
currentEndPosition = new FileInfo (CapturePath).Length;
|
|
|
|
var length = (int) (currentEndPosition - startPosition);
|
|
var currentLength = new FileInfo (CapturePath).Length;
|
|
var capturedLength = 0L;
|
|
|
|
if (length < 0) {
|
|
// The file shrank?
|
|
return;
|
|
}
|
|
|
|
if (File.Exists (Path))
|
|
capturedLength = new FileInfo (Path).Length;
|
|
|
|
// capture 1k more data than when we stopped, since the system log
|
|
// is cached in memory and flushed once in a while (so when the app
|
|
// requests the system log to be captured, it's usually not complete).
|
|
var availableLength = currentLength - startPosition;
|
|
if (availableLength <= capturedLength)
|
|
return; // We've captured before, and nothing new as added since last time.
|
|
|
|
// Capture at most 1k more
|
|
availableLength = Math.Min (availableLength, length + 1024);
|
|
|
|
using (var reader = new FileStream (CapturePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) {
|
|
using (var writer = new FileStream (Path, FileMode.Create, FileAccess.Write, FileShare.Read)) {
|
|
var buffer = new byte [4096];
|
|
reader.Position = startPosition;
|
|
while (availableLength > 0) {
|
|
int read = reader.Read (buffer, 0, Math.Min (buffer.Length, length));
|
|
if (read > 0) {
|
|
writer.Write (buffer, 0, read);
|
|
availableLength -= read;
|
|
} else {
|
|
// There's nothing more to read.
|
|
// I can't see how we get here, since we calculate the amount to read based on what's available, but it does happen randomly.
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public override StreamReader GetReader ()
|
|
{
|
|
if (File.Exists (CapturePath)) {
|
|
return new StreamReader (new FileStream (CapturePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
|
|
} else {
|
|
return new StreamReader (new MemoryStream ());
|
|
}
|
|
}
|
|
|
|
public override void Flush ()
|
|
{
|
|
base.Flush ();
|
|
|
|
Capture ();
|
|
}
|
|
|
|
public override void WriteImpl (string value)
|
|
{
|
|
throw new InvalidOperationException ();
|
|
}
|
|
|
|
public override string FullPath {
|
|
get {
|
|
return Path;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
public override string FullPath => throw new NotImplementedException ();
|
|
|
|
public override void WriteImpl (string value)
|
|
{
|
|
OnWrite (value);
|
|
}
|
|
}
|
|
}
|
|
|