xamarin-macios/tests/xharness/Log.cs

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);
}
}
}