mirror of
https://github.com/beefytech/Beef.git
synced 2025-06-08 03:28:20 +02:00

Throwing error on member references with ".." cascade token outside invocations (ie: "ts..mA = 123") Fixed 'Thread.ModuleTLSIndex' error - which caused us TLS lookup failures in Beef DLLs Fixed some hotswap errors Made BeefPerf shut down properly Fixed an 'int literal' FixIntUnknown issue where rhs was System.Object which caused an illegal boxing Fixed COFF::LocateSymbol issues with Win32 and also with linking to static libraries - showed up with hot-linking in fmod when hot-adding a floating point mod Fixed a couple memory leaks Fixed alignment issue in COFF::ParseCompileUnit
493 lines
12 KiB
Beef
493 lines
12 KiB
Beef
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Threading;
|
|
using System.IO;
|
|
|
|
namespace IDE
|
|
{
|
|
class TestManager
|
|
{
|
|
public class ProjectInfo
|
|
{
|
|
public Project mProject;
|
|
public String mTestExePath ~ delete _;
|
|
}
|
|
|
|
public class TestEntry
|
|
{
|
|
public String mName ~ delete _;
|
|
public String mFilePath ~ delete _;
|
|
public int mLine;
|
|
public int mColumn;
|
|
public bool mShouldFail;
|
|
public bool mProfile;
|
|
public bool mIgnore;
|
|
}
|
|
|
|
public class TestInstance
|
|
{
|
|
public SpawnedProcess mProcess ~ delete _;
|
|
public Thread mThread ~ delete _;
|
|
public int mProjectIdx;
|
|
public List<TestEntry> mTestEntries = new .() ~ DeleteContainerAndItems!(_);
|
|
public String mPipeName ~ delete _;
|
|
public String mArgs ~ delete _;
|
|
public String mWorkingDir ~ delete _;
|
|
public NamedPipe mPipeServer ~ delete _;
|
|
public int mShouldFailIdx = -1;
|
|
}
|
|
|
|
public bool mIsDone;
|
|
public bool mIsRunning;
|
|
public bool mWantsStop;
|
|
public int mProjectInfoIdx = -1;
|
|
public TestInstance mTestInstance ~ delete _;
|
|
public List<ProjectInfo> mProjectInfos = new .() ~ DeleteContainerAndItems!(_);
|
|
public List<String> mQueuedOutput = new .() ~ DeleteContainerAndItems!(_);
|
|
public Monitor mMonitor = new Monitor() ~ delete _;
|
|
public String mPrevConfigName ~ delete _;
|
|
public bool mDebug;
|
|
public bool mIncludeIgnored;
|
|
public bool mFailed;
|
|
|
|
public ~this()
|
|
{
|
|
if (mTestInstance != null)
|
|
{
|
|
mTestInstance.mThread.Join();
|
|
}
|
|
}
|
|
|
|
public bool IsRunning()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public void AddProject(Project project)
|
|
{
|
|
var projectInfo = new ProjectInfo();
|
|
projectInfo.mProject = project;
|
|
mProjectInfos.Add(projectInfo);
|
|
}
|
|
|
|
public bool IsTesting(Project project)
|
|
{
|
|
return GetProjectInfo(project) != null;
|
|
}
|
|
|
|
public ProjectInfo GetProjectInfo(Project project)
|
|
{
|
|
int projectIdx = mProjectInfos.FindIndex(scope (info) => info.mProject == project);
|
|
if (projectIdx == -1)
|
|
return null;
|
|
return mProjectInfos[projectIdx];
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
mIsRunning = true;
|
|
}
|
|
|
|
public void BuildFailed()
|
|
{
|
|
mIsDone = true;
|
|
}
|
|
|
|
ProjectInfo GetCurProjectInfo()
|
|
{
|
|
if ((mProjectInfoIdx >= 0) && (mProjectInfoIdx < mProjectInfos.Count))
|
|
return mProjectInfos[mProjectInfoIdx];
|
|
return null;
|
|
}
|
|
|
|
void QueueOutputLine(StringView str)
|
|
{
|
|
using (mMonitor.Enter())
|
|
mQueuedOutput.Add(new String(str));
|
|
}
|
|
|
|
void QueueOutputLine(StringView str, params Object[] args)
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
var formattedStr = new String();
|
|
formattedStr.AppendF(str, params args);
|
|
mQueuedOutput.Add(formattedStr);
|
|
}
|
|
}
|
|
|
|
public void TestProc(TestInstance testInstance)
|
|
{
|
|
var curProjectInfo = GetCurProjectInfo();
|
|
|
|
if (!mDebug)
|
|
{
|
|
var startInfo = scope ProcessStartInfo();
|
|
startInfo.CreateNoWindow = !gApp.mTestEnableConsole;
|
|
startInfo.SetFileName(curProjectInfo.mTestExePath);
|
|
startInfo.SetArguments(testInstance.mArgs);
|
|
startInfo.SetWorkingDirectory(testInstance.mWorkingDir);
|
|
mTestInstance.mProcess = new SpawnedProcess();
|
|
if (testInstance.mProcess.Start(startInfo) case .Err)
|
|
{
|
|
TestFailed();
|
|
QueueOutputLine("ERROR: Failed execute '{0}'", curProjectInfo.mTestExePath);
|
|
return;
|
|
}
|
|
}
|
|
|
|
String clientStr = scope String();
|
|
|
|
int curTestIdx = -1;
|
|
int curTestRunCount = 0;
|
|
bool testsFinished = false;
|
|
bool failed = false;
|
|
|
|
int exitCode = 0;
|
|
|
|
while (true)
|
|
{
|
|
int doneCount = 0;
|
|
|
|
for (int itr < 2)
|
|
{
|
|
bool hadData = false;
|
|
|
|
uint8[1024] data;
|
|
switch (testInstance.mPipeServer.TryRead(.(&data, 1024), 20))
|
|
{
|
|
case .Ok(let size):
|
|
{
|
|
clientStr.Append((char8*)&data, size);
|
|
hadData = true;
|
|
}
|
|
default:
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
int crPos = clientStr.IndexOf('\n');
|
|
if (crPos == -1)
|
|
break;
|
|
|
|
String cmd = scope String();
|
|
cmd.Append(clientStr, 0, crPos);
|
|
clientStr.Remove(0, crPos + 1);
|
|
|
|
/*String outStr = scope String();
|
|
outStr.AppendF("CMD: {0}", cmd);
|
|
QueueOutput(outStr);*/
|
|
|
|
List<StringView> cmdParts = scope .(cmd.Split('\t'));
|
|
switch (cmdParts[0])
|
|
{
|
|
case ":TestInit":
|
|
case ":TestBegin":
|
|
case ":TestQuery":
|
|
if ((curTestIdx == -1) || (curTestRunCount > 0))
|
|
{
|
|
curTestIdx++;
|
|
curTestRunCount = 0;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (curTestIdx < testInstance.mTestEntries.Count)
|
|
{
|
|
curTestRunCount++;
|
|
bool skipEntry = false;
|
|
|
|
let testEntry = testInstance.mTestEntries[curTestIdx];
|
|
if (testEntry.mShouldFail)
|
|
{
|
|
skipEntry = testInstance.mShouldFailIdx != curTestIdx;
|
|
}
|
|
else if (testInstance.mShouldFailIdx != -1)
|
|
{
|
|
skipEntry = true;
|
|
}
|
|
|
|
if ((!skipEntry) && (testEntry.mIgnore) && (!mIncludeIgnored))
|
|
{
|
|
QueueOutputLine("Test Ignored: {0}", testEntry.mName);
|
|
skipEntry = true;
|
|
}
|
|
|
|
if (skipEntry)
|
|
{
|
|
curTestIdx++;
|
|
curTestRunCount = 0;
|
|
continue;
|
|
}
|
|
|
|
var clientCmd = scope String();
|
|
clientCmd.AppendF(":TestRun\t{0}\n", curTestIdx);
|
|
if (testInstance.mPipeServer.Write(clientCmd) case .Err)
|
|
failed = true;
|
|
}
|
|
else
|
|
{
|
|
if (testInstance.mPipeServer.Write(":TestFinish\n") case .Err)
|
|
failed = true;
|
|
}
|
|
break;
|
|
}
|
|
case ":TestResult":
|
|
int timeMS = int32.Parse(cmdParts[1]).Get();
|
|
var testEntry = testInstance.mTestEntries[curTestIdx];
|
|
if (testEntry.mShouldFail)
|
|
{
|
|
QueueOutputLine("ERROR: Test should have failed but didn't: {0} Time: {1}ms", testEntry.mName, timeMS);
|
|
failed = true;
|
|
}
|
|
else
|
|
QueueOutputLine("Test completed: {0} Time: {1}ms", testEntry.mName, timeMS);
|
|
case ":TestFinish":
|
|
testsFinished = true;
|
|
default:
|
|
Debug.Assert(cmdParts[0][0] != ':');
|
|
|
|
let attribs = cmdParts[1];
|
|
|
|
TestEntry testEntry = new TestEntry();
|
|
testEntry.mName = new String(cmdParts[0]);
|
|
testEntry.mFilePath = new String(cmdParts[2]);
|
|
testEntry.mLine = int32.Parse(cmdParts[3]).Get();
|
|
testEntry.mColumn = int32.Parse(cmdParts[4]).Get();
|
|
|
|
testEntry.mShouldFail = attribs.Contains("Sf");
|
|
testEntry.mProfile = attribs.Contains("Pr");
|
|
testEntry.mIgnore = attribs.Contains("Ig");
|
|
|
|
testInstance.mTestEntries.Add(testEntry);
|
|
}
|
|
}
|
|
|
|
if (mWantsStop)
|
|
{
|
|
if (testInstance.mProcess != null)
|
|
testInstance.mProcess.Kill();
|
|
}
|
|
|
|
if (!hadData)
|
|
{
|
|
bool processDone;
|
|
if (testInstance.mProcess != null)
|
|
processDone = testInstance.mProcess.WaitFor(0);
|
|
else
|
|
processDone = !gApp.mDebugger.mIsRunning;
|
|
|
|
if (processDone)
|
|
{
|
|
if (testInstance.mProcess != null)
|
|
{
|
|
exitCode = testInstance.mProcess.ExitCode;
|
|
}
|
|
|
|
doneCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (doneCount == 2)
|
|
break;
|
|
|
|
if (failed)
|
|
{
|
|
TestFailed();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (mWantsStop)
|
|
{
|
|
QueueOutputLine("Tests aborted");
|
|
}
|
|
else if (!testsFinished)
|
|
{
|
|
var str = scope String();
|
|
if (curTestIdx == -1)
|
|
{
|
|
str.AppendF("Failed to start tests");
|
|
}
|
|
else if (curTestIdx < testInstance.mTestEntries.Count)
|
|
{
|
|
var testEntry = testInstance.mTestEntries[curTestIdx];
|
|
if (testInstance.mShouldFailIdx == curTestIdx)
|
|
{
|
|
// Success
|
|
QueueOutputLine("Test expectedly failed: {0}", testEntry.mName);
|
|
}
|
|
else
|
|
{
|
|
str.AppendF("ERROR: Failed test '{0}' at line {2}:{3} in {1}", testEntry.mName, testEntry.mFilePath, testEntry.mLine + 1, testEntry.mColumn + 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
str.AppendF("ERROR: Failed to finish tests");
|
|
}
|
|
|
|
if (str.Length > 0)
|
|
{
|
|
var errStr = scope String();
|
|
errStr.AppendF("ERROR: {0}", str);
|
|
QueueOutputLine(errStr);
|
|
TestFailed();
|
|
}
|
|
}
|
|
else if (exitCode != 0)
|
|
{
|
|
if (exitCode != 0)
|
|
{
|
|
QueueOutputLine("ERROR: Test process exited with error code: {0}", exitCode);
|
|
TestFailed();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
using (mMonitor.Enter())
|
|
{
|
|
while (mQueuedOutput.Count > 0)
|
|
{
|
|
var str = mQueuedOutput.PopFront();
|
|
gApp.OutputLineSmart(str);
|
|
delete str;
|
|
}
|
|
}
|
|
|
|
if ((!mIsRunning) || (mIsDone))
|
|
return;
|
|
|
|
if (mWantsStop)
|
|
{
|
|
if (gApp.mDebugger.mIsRunning)
|
|
gApp.mDebugger.Terminate();
|
|
}
|
|
|
|
int nextShouldFailIdx = -1;
|
|
bool doNext = true;
|
|
var curProjectInfo = GetCurProjectInfo();
|
|
if (curProjectInfo != null)
|
|
{
|
|
if (mTestInstance != null)
|
|
{
|
|
if (mTestInstance.mThread.Join(0))
|
|
{
|
|
for (int entryIdx = mTestInstance.mShouldFailIdx + 1; entryIdx < mTestInstance.mTestEntries.Count; entryIdx++)
|
|
{
|
|
let testEntry = mTestInstance.mTestEntries[entryIdx];
|
|
if (testEntry.mShouldFail)
|
|
{
|
|
nextShouldFailIdx = entryIdx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DeleteAndNullify!(mTestInstance);
|
|
}
|
|
else
|
|
doNext = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Debug.Assert(mTestInstance == null);
|
|
}
|
|
|
|
if (doNext)
|
|
{
|
|
if (mWantsStop)
|
|
{
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
|
|
Debug.Assert(mTestInstance == null);
|
|
|
|
if (nextShouldFailIdx == -1)
|
|
{
|
|
mProjectInfoIdx++;
|
|
if (mProjectInfoIdx >= mProjectInfos.Count)
|
|
{
|
|
mIsDone = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
mTestInstance = new TestInstance();
|
|
mTestInstance.mProjectIdx = mProjectInfoIdx;
|
|
mTestInstance.mShouldFailIdx = nextShouldFailIdx;
|
|
|
|
curProjectInfo = GetCurProjectInfo();
|
|
if (mTestInstance.mShouldFailIdx != -1)
|
|
gApp.OutputLineSmart("Starting should-fail testing on {0}...", curProjectInfo.mProject.mProjectName);
|
|
else
|
|
gApp.OutputLineSmart("Starting testing on {0}...", curProjectInfo.mProject.mProjectName);
|
|
|
|
mTestInstance.mThread = new Thread(new () => { TestProc(mTestInstance); } );
|
|
|
|
mTestInstance.mPipeName = new String();
|
|
mTestInstance.mPipeName.AppendF("__bfTestPipe{0}_{1}", Process.CurrentId, mTestInstance.mProjectIdx);
|
|
|
|
mTestInstance.mArgs = new String();
|
|
mTestInstance.mArgs.Append(mTestInstance.mPipeName);
|
|
|
|
//mTestInstance.mWorkingDir = new String();
|
|
//Path.GetDirectoryName(curProjectInfo.mTestExePath, mTestInstance.mWorkingDir);
|
|
mTestInstance.mWorkingDir = new String(gApp.mInstallDir);
|
|
|
|
mTestInstance.mPipeServer = new NamedPipe();
|
|
if (mTestInstance.mPipeServer.Create(".", mTestInstance.mPipeName, .AllowTimeouts) case .Err)
|
|
{
|
|
QueueOutputLine("ERROR: Failed to create named pipe for test");
|
|
TestFailed();
|
|
return;
|
|
}
|
|
|
|
if (mDebug)
|
|
{
|
|
gApp.[Friend]CheckDebugVisualizers();
|
|
|
|
var envVars = scope Dictionary<String, String>();
|
|
defer { for (var kv in envVars) { delete kv.key; delete kv.value; } }
|
|
Environment.GetEnvironmentVariables(envVars);
|
|
|
|
var envBlock = scope List<char8>();
|
|
Environment.EncodeEnvironmentVariables(envVars, envBlock);
|
|
if (!gApp.mDebugger.OpenFile(curProjectInfo.mTestExePath, curProjectInfo.mTestExePath, mTestInstance.mArgs, mTestInstance.mWorkingDir, envBlock, true))
|
|
{
|
|
QueueOutputLine("ERROR: Failed debug '{0}'", curProjectInfo.mTestExePath);
|
|
TestFailed();
|
|
return;
|
|
}
|
|
|
|
gApp.mDebugger.ClearInvalidBreakpoints();
|
|
gApp.mTargetDidInitBreak = false;
|
|
gApp.mTargetHadFirstBreak = false;
|
|
|
|
gApp.mDebugger.RehupBreakpoints(true);
|
|
gApp.mDebugger.Run();
|
|
gApp.mDebugger.mIsRunning = true;
|
|
}
|
|
|
|
mTestInstance.mThread.Start(false);
|
|
}
|
|
}
|
|
|
|
public void TestFailed()
|
|
{
|
|
gApp.TestFailed();
|
|
mIsDone = true;
|
|
mFailed = true;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
mWantsStop = true;
|
|
}
|
|
}
|
|
}
|