未验证 提交 ce6d3dff 编写于 作者: A Ankit Jain 提交者: GitHub

[wasm] Wasm.Build.Tests - fixes for tests failing on CI (#70704)

* [wasm][nodejs] Ensure that stdout/stderr have been flushed out before exiting

When the results xml is large, and we are writing the base64
representation in one line, `node` can exit before all the output gets
flushed out. This results in xharness getting an incomplete
`STARTRESULTXML <len> <base64> ... ` with missing `ENDRESULTXML`, thus
no `testResults.xml` is generated.

This can be seen in the case of `Microsoft.Extensions.Primitives.Tests`
which has xml ~140KB, and `System.Memory.Tests` which has a xml ~13MB.

So, wait for the two streams to be flushed out, with a timeout of 3secs.

* [wasm] Wasm.Build.Tests: improve reading output from processes

- Fix to call `WaitForExit()` once `WaitForExit(int)` returns, which
  ensures that all the async handlers have been run.

- Also, for non-browser xharness runs use the emitted `wasm-console.log`
  as the output, so we don't depend on xharness' stdout.

* Wasm.Build.Tests: Run the crypto test only with browser. Other cases are covered in library tests

* Enable WasmTemplateTests.ConsolePublishAndRun

Fixes https://github.com/dotnet/runtime/issues/70675

* Wasm.Build.Tests: avoid unncessary copy when building locally

Copy sdk for testing workloads only on CI.

* Address feedback from @kg

* Remove timeout on flushing stdout/stderr streams, for console templates, IOW, user apps
上级 9e703be9
......@@ -47,11 +47,25 @@ function set_exit_code(exit_code, reason) {
}
if (App && App.INTERNAL) {
App.INTERNAL.mono_wasm_exit(exit_code);
let _flush = function(_stream) {
return new Promise((resolve, reject) => {
_stream.on('error', (error) => reject(error));
_stream.write('', function() { resolve () });
});
};
let stderrFlushed = _flush(process.stderr);
let stdoutFlushed = _flush(process.stdout);
Promise.all([ stdoutFlushed, stderrFlushed ])
.then(
() => App.INTERNAL.mono_wasm_exit(exit_code),
reason => {
console.error(`flushing std* streams failed: ${reason}`);
App.INTERNAL.mono_wasm_exit(123456);
});
}
}
let runArgs = {};
let is_debugging = false;
......
......@@ -105,26 +105,23 @@ function set_exit_code(exit_code, reason) {
} else if (App && App.INTERNAL) {
if (is_node) {
let _flush = function (_stream) {
return new Promise((resolve, reject) => {
if (!_stream.write('')) {
_stream.on('drain', () => resolve());
setTimeout(reject, 3000);
} else {
resolve();
}
});
let _flush = function(_stream) {
return new Promise((resolve, reject) => {
setTimeout(() => { reject(new Error("timed out waiting for stdout/stderr streams to flush")) }, 30000);
_stream.on('error', (error) => reject(error));
_stream.write('', function() { resolve () });
});
};
let stderrFlushed = _flush(process.stderr);
let stdoutFlushed = _flush(process.stdout);
Promise.all([stdoutFlushed, stderrFlushed])
.then(
() => App.INTERNAL.mono_wasm_exit(exit_code),
reason => {
console.error(`flushing std* streams failed: ${reason}`);
App.INTERNAL.mono_wasm_exit(123);
});
Promise.all([ stdoutFlushed, stderrFlushed ])
.then(
() => App.INTERNAL.mono_wasm_exit(exit_code),
reason => {
console.error(`flushing std* streams failed: ${reason}`);
App.INTERNAL.mono_wasm_exit(123456);
});
} else {
App.INTERNAL.mono_wasm_exit(exit_code);
}
......
......@@ -152,11 +152,13 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
}
string bundleDir = Path.Combine(GetBinDir(baseDir: buildDir, config: buildArgs.Config, targetFramework: targetFramework), "AppBundle");
(string testCommand, string extraXHarnessArgs) = host switch
// Use wasm-console.log to get the xharness output for non-browser cases
(string testCommand, string extraXHarnessArgs, bool useWasmConsoleOutput) = host switch
{
RunHost.V8 => ("wasm test", "--js-file=test-main.js --engine=V8 -v trace"),
RunHost.NodeJS => ("wasm test", "--js-file=test-main.js --engine=NodeJS -v trace"),
_ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop")
RunHost.V8 => ("wasm test", "--js-file=test-main.js --engine=V8 -v trace", true),
RunHost.NodeJS => ("wasm test", "--js-file=test-main.js --engine=NodeJS -v trace", true),
_ => ("wasm test-browser", $"-v trace -b {host} --web-server-use-cop", false)
};
string testLogPath = Path.Combine(_logPath, host.ToString());
......@@ -170,7 +172,8 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
expectedAppExitCode: expectedExitCode,
extraXHarnessArgs: extraXHarnessArgs,
appArgs: args,
extraXHarnessMonoArgs: extraXHarnessMonoArgs
extraXHarnessMonoArgs: extraXHarnessMonoArgs,
useWasmConsoleOutput: useWasmConsoleOutput
);
if (buildArgs.AOT)
......@@ -192,7 +195,8 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
protected static string RunWithXHarness(string testCommand, string testLogPath, string projectName, string bundleDir,
ITestOutputHelper _testOutput, IDictionary<string, string>? envVars=null,
int expectedAppExitCode=0, int xharnessExitCode=0, string? extraXHarnessArgs=null, string? appArgs=null, string? extraXHarnessMonoArgs = null)
int expectedAppExitCode=0, int xharnessExitCode=0, string? extraXHarnessArgs=null,
string? appArgs=null, string? extraXHarnessMonoArgs = null, bool useWasmConsoleOutput = false)
{
_testOutput.WriteLine($"============== {testCommand} =============");
Directory.CreateDirectory(testLogPath);
......@@ -230,6 +234,21 @@ public BuildTestBase(ITestOutputHelper output, SharedBuildPerTestClassFixture bu
timeoutMs: s_defaultPerTestTimeoutMs);
File.WriteAllText(Path.Combine(testLogPath, $"xharness.log"), output);
if (useWasmConsoleOutput)
{
string wasmConsolePath = Path.Combine(testLogPath, "wasm-console.log");
try
{
if (File.Exists(wasmConsolePath))
output = File.ReadAllText(wasmConsolePath);
else
_testOutput.WriteLine($"Warning: Could not find {wasmConsolePath}. Ignoring.");
}
catch (IOException ioex)
{
_testOutput.WriteLine($"Warning: Could not read {wasmConsolePath}: {ioex}");
}
}
if (exitCode != xharnessExitCode)
{
......@@ -753,9 +772,12 @@ protected string GetObjDir(string config, string targetFramework=DefaultTargetFr
process.BeginOutputReadLine();
process.BeginErrorReadLine();
// process.WaitForExit doesn't work if the process exits too quickly?
// resetEvent.WaitOne();
if (!process.WaitForExit(timeoutMs ?? s_defaultPerTestTimeoutMs))
using CancellationTokenSource cts = new();
cts.CancelAfter(timeoutMs ?? s_defaultPerTestTimeoutMs);
await process.WaitForExitAsync(cts.Token);
if (cts.IsCancellationRequested)
{
// process didn't exit
process.Kill(entireProcessTree: true);
......@@ -765,13 +787,11 @@ protected string GetObjDir(string config, string targetFramework=DefaultTargetFr
throw new XunitException($"Process timed out. Last 20 lines of output:{Environment.NewLine}{string.Join(Environment.NewLine, lastLines)}");
}
}
else
{
// this will ensure that all the async event handling
// has completed
// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit?view=net-5.0#System_Diagnostics_Process_WaitForExit_System_Int32_
await process.WaitForExitAsync();
}
// this will ensure that all the async event handling has completed
// and should be called after process.WaitForExit(int)
// https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit?view=net-5.0#System_Diagnostics_Process_WaitForExit_System_Int32_
process.WaitForExit();
process.ErrorDataReceived -= logStdErr;
process.OutputDataReceived -= logStdOut;
......
......@@ -98,8 +98,8 @@ public static int Main()
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[BuildAndRun(aot: false)]
[BuildAndRun(aot: true)]
[BuildAndRun(aot: false, host: RunHost.Chrome)]
[BuildAndRun(aot: true, host: RunHost.Chrome)]
public void ProjectUsingBrowserNativeCrypto(BuildArgs buildArgs, RunHost host, string id)
{
string projectName = $"AppUsingBrowserNativeCrypto";
......@@ -140,14 +140,7 @@ public static int Main()
output);
string cryptoInitMsg = "MONO_WASM: Initializing Crypto WebWorker";
if (host == RunHost.V8 || host == RunHost.NodeJS)
{
Assert.DoesNotContain(cryptoInitMsg, output);
}
else
{
Assert.Contains(cryptoInitMsg, output);
}
Assert.Contains(cryptoInitMsg, output);
}
}
}
......@@ -149,7 +149,6 @@ public void ConsoleBuildAndRun(string config)
}
[ConditionalTheory(typeof(BuildTestBase), nameof(IsUsingWorkloads))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/70675", TestPlatforms.Windows)]
[InlineData("Debug", false)]
[InlineData("Debug", true)]
[InlineData("Release", false)]
......
......@@ -94,16 +94,21 @@ exit /b %EXIT_CODE%
REM Functions
:SetEnvVars
if [%TEST_USING_WORKLOADS%] == [true] (
set _DIR_NAME=dotnet-workload
set SDK_HAS_WORKLOAD_INSTALLED=true
robocopy /np /nfl /e %BASE_DIR%\dotnet-workload %EXECUTION_DIR%\dotnet-workload
set "SDK_FOR_WORKLOAD_TESTING_PATH=%EXECUTION_DIR%\dotnet-workload"
set "PATH=%EXECUTION_DIR%\dotnet-workload;%PATH%"
set "AppRefDir=%BASE_DIR%\microsoft.netcore.app.ref"
) else (
set _DIR_NAME=sdk-no-workload
set SDK_HAS_WORKLOAD_INSTALLED=false
robocopy /np /nfl /e %BASE_DIR%\sdk-no-workload %EXECUTION_DIR%\sdk-no-workload
set "SDK_FOR_WORKLOAD_TESTING_PATH=%EXECUTION_DIR%\sdk-no-workload"
set "PATH=%EXECUTION_DIR%\sdk-no-workload;%PATH%"
set "AppRefDir=%BASE_DIR%\microsoft.netcore.app.ref"
)
if [%HELIX_CORRELATION_PAYLOAD%] NEQ [] (
robocopy /np /nfl /NDL /NJH /NJS /nc /e %BASE_DIR%\%_DIR_NAME% %EXECUTION_DIR%\%_DIR_NAME%
set _SDK_DIR=%EXECUTION_DIR%\%_DIR_NAME%
) else (
set _SDK_DIR=%BASE_DIR%\%_DIR_NAME%
)
set "PATH=%_SDK_DIR%;%PATH%"
set "SDK_FOR_WORKLOAD_TESTING_PATH=%_SDK_DIR%"
set "AppRefDir=%BASE_DIR%\microsoft.netcore.app.ref"
EXIT /b 0
......@@ -66,19 +66,26 @@ echo XHARNESS_ARGS=$XHARNESS_ARGS
function set_env_vars()
{
local _DIR_NAME=
if [ "x$TEST_USING_WORKLOADS" = "xtrue" ]; then
cp -r $BASE_DIR/dotnet-workload $EXECUTION_DIR
export PATH=$EXECUTION_DIR/dotnet-workload:$PATH
_DIR_NAME=dotnet-workload
export SDK_HAS_WORKLOAD_INSTALLED=true
export SDK_FOR_WORKLOAD_TESTING_PATH=$EXECUTION_DIR/dotnet-workload
export AppRefDir=$BASE_DIR/microsoft.netcore.app.ref
else
cp -r $BASE_DIR/sdk-no-workload $EXECUTION_DIR
export PATH=$EXECUTION_DIR/sdk-no-workload:$PATH
_DIR_NAME=sdk-no-workload
export SDK_HAS_WORKLOAD_INSTALLED=false
export SDK_FOR_WORKLOAD_TESTING_PATH=$EXECUTION_DIR/sdk-no-workload
export AppRefDir=$BASE_DIR/microsoft.netcore.app.ref
fi
local _SDK_DIR=
if [[ -n "$HELIX_WORKITEM_UPLOAD_ROOT" ]]; then
cp -r $BASE_DIR/$_DIR_NAME $EXECUTION_DIR
_SDK_DIR=$EXECUTION_DIR/$_DIR_NAME
else
_SDK_DIR=$BASE_DIR/$_DIR_NAME
fi
export PATH=$_SDK_DIR:$PATH
export SDK_FOR_WORKLOAD_TESTING_PATH=$_SDK_DIR
export AppRefDir=$BASE_DIR/microsoft.netcore.app.ref
}
export TEST_LOG_PATH=${XHARNESS_OUT}/logs
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册