Skip to content
Merged
1 change: 1 addition & 0 deletions eng/testing/scenarios/BuildWasmAppsJobsList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Wasm.Build.Tests.TestAppScenarios.LazyLoadingTests
Wasm.Build.Tests.TestAppScenarios.LibraryInitializerTests
Wasm.Build.Tests.TestAppScenarios.SatelliteLoadingTests
Wasm.Build.Tests.TestAppScenarios.ModuleConfigTests
Wasm.Build.Tests.TestAppScenarios.MemoryTests
Wasm.Build.Tests.AspNetCore.SignalRClientTests
Wasm.Build.Tests.WasmBuildAppTest
Wasm.Build.Tests.WasmNativeDefaultsTests
Expand Down
37 changes: 37 additions & 0 deletions src/mono/wasm/Wasm.Build.Tests/TestAppScenarios/MemoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit.Abstractions;
using Xunit;

#nullable enable

namespace Wasm.Build.Tests.TestAppScenarios;

public class MemoryTests : AppTestBase
{
public MemoryTests(ITestOutputHelper output, SharedBuildPerTestClassFixture buildContext)
: base(output, buildContext)
{
}

[Theory]
[InlineData("Release", true)]
[InlineData("Release", false)]
public async Task AllocateLargeHeapThenRepeatedlyInterop(string config, bool buildNative)
{
// native build triggers passing value form EmccMaximumHeapSize to MAXIMUM_MEMORY that is set in emscripten
// in non-native build EmccMaximumHeapSize does not have an effect, so the test will fail with "out of memory"
CopyTestAsset("WasmBasicTestApp", "MemoryTests", "App");
string extraArgs = $"-p:EmccMaximumHeapSize=4294901760 -p:WasmBuildNative={buildNative}";
BuildProject(config, assertAppBundle: false, extraArgs: extraArgs);

var result = await RunSdkStyleAppForBuild(new (Configuration: config, TestScenario: "AllocateLargeHeapThenInterop", ExpectedExitCode: buildNative ? 0 : 1));
if (!buildNative)
Assert.Contains(result.TestOutput, item => item.Contains("Exception System.OutOfMemoryException: Out of memory"));
}
}
74 changes: 74 additions & 0 deletions src/mono/wasm/testassets/WasmBasicTestApp/App/MemoryTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Text.Json;
using System.Text;
using System.Runtime.InteropServices.JavaScript;

public partial class MemoryTest // ?test=AllocateLargeHeapThenInterop
{
[JSImport("countChars", "main.js")]
internal static partial int CountChars(string testArray);

[JSExport]
internal static void Run()
{
// Allocate over 2GB space, 2 621 440 000 bytes
const int arrayCnt = 25;
int[][] arrayHolder = new int[arrayCnt][];
string errors = "";
TestOutput.WriteLine("Starting over 2GB array allocation");
for (int i = 0; i < arrayCnt; i++)
{
try
{
arrayHolder[i] = new int[1024 * 1024 * 25];
}
catch (Exception ex)
{
errors += $"Exception {ex} was thrown on i={i}";
}
}
TestOutput.WriteLine("Finished over 2GB array allocation");

// call a method many times to trigger tier-up optimization
string randomString = GenerateRandomString(1000);
try
{
for (int i = 0; i < 10000; i++)
{
int count = CountChars(randomString);
if (count != randomString.Length)
errors += $"CountChars returned {count} instead of {randomString.Length} for {i}-th string.";
}
}
catch (Exception ex)
{
errors += $"Exception {ex} was thrown when CountChars was called in a loop";
}
if (!string.IsNullOrEmpty(errors))
{
TestOutput.WriteLine(errors);
throw new Exception(errors);
}
else
{
TestOutput.WriteLine("Great success, MemoryTest finished without errors.");
}
}

private static Random random = new Random();

private static string GenerateRandomString(int stringLength)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
var stringBuilder = new StringBuilder(stringLength);
for (int i = 0; i < stringLength; i++)
{
stringBuilder.Append(chars[random.Next(chars.Length)]);
}
return stringBuilder.ToString();
}
}
13 changes: 13 additions & 0 deletions src/mono/wasm/testassets/WasmBasicTestApp/App/wwwroot/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ function testOutput(msg) {
console.log(`TestOutput -> ${msg}`);
}

function countChars(str) {
const length = str.length;
testOutput(`JS received str of ${length} length`);
return length;
}

// Prepare base runtime parameters
dotnet
.withElementOnExit()
Expand Down Expand Up @@ -168,6 +174,13 @@ try {
case "MaxParallelDownloads":
exit(0);
break;
case "AllocateLargeHeapThenInterop":
setModuleImports('main.js', {
countChars
});
exports.MemoryTest.Run();
exit(0);
break;
default:
console.error(`Unknown test case: ${testCase}`);
exit(3);
Expand Down