Skip to content

Commit

Permalink
Merge pull request #15 from bonsai-rx/automated-tests
Browse files Browse the repository at this point in the history
Add support for unit testing
  • Loading branch information
glopesdev authored Jun 28, 2024
2 parents aeb9c45 + ebff6af commit cea88fc
Show file tree
Hide file tree
Showing 11 changed files with 596 additions and 46 deletions.
10 changes: 6 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ jobs:
with:
submodules: true

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Setup .NET Core SDK
uses: actions/setup-dotnet@v4.0.0
with:
dotnet-version: 8.x

- name: Restore NuGet Packages
run: msbuild -t:restore src/Bonsai.ML.sln
run: dotnet restore Bonsai.ML.sln

- name: Build Solution
run: msbuild src/Bonsai.ML.sln /p:Configuration=Release
run: dotnet build Bonsai.ML.sln -c Release
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Builds and runs unit tests for the examples
name: Test Examples

on:
workflow_dispatch:

jobs:
test:
runs-on: windows-latest
steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: 3.10

- name: Build Solution
uses: ./.github/workflows/build.yml

- name: Run Tests
run: dotnet test Bonsai.ML.sln
50 changes: 50 additions & 0 deletions Bonsai.ML.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{12312384-8828-4786-AE19-EFCEDF968290}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML", "src\Bonsai.ML\Bonsai.ML.csproj", "{AA6BE73F-1E15-49A5-AC3C-CD069035C940}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.LinearDynamicalSystems", "src\Bonsai.ML.LinearDynamicalSystems\Bonsai.ML.LinearDynamicalSystems.csproj", "{17AABD18-E275-4409-9E33-3D755B809FF6}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.Visualizers", "src\Bonsai.ML.Visualizers\Bonsai.ML.Visualizers.csproj", "{196AA5C7-AE8A-477B-B01A-B94676EC60EE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{461FE3E2-21C4-47F9-8405-DF72326AAB2B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bonsai.ML.LinearDynamicalSystems.Tests", "tests\Bonsai.ML.LinearDynamicalSystems.Tests\Bonsai.ML.LinearDynamicalSystems.Tests.csproj", "{81DB65B3-EA65-4947-8CF1-0E777324C082}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AA6BE73F-1E15-49A5-AC3C-CD069035C940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AA6BE73F-1E15-49A5-AC3C-CD069035C940}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AA6BE73F-1E15-49A5-AC3C-CD069035C940}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AA6BE73F-1E15-49A5-AC3C-CD069035C940}.Release|Any CPU.Build.0 = Release|Any CPU
{17AABD18-E275-4409-9E33-3D755B809FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{17AABD18-E275-4409-9E33-3D755B809FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17AABD18-E275-4409-9E33-3D755B809FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17AABD18-E275-4409-9E33-3D755B809FF6}.Release|Any CPU.Build.0 = Release|Any CPU
{196AA5C7-AE8A-477B-B01A-B94676EC60EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{196AA5C7-AE8A-477B-B01A-B94676EC60EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{196AA5C7-AE8A-477B-B01A-B94676EC60EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{196AA5C7-AE8A-477B-B01A-B94676EC60EE}.Release|Any CPU.Build.0 = Release|Any CPU
{81DB65B3-EA65-4947-8CF1-0E777324C082}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81DB65B3-EA65-4947-8CF1-0E777324C082}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81DB65B3-EA65-4947-8CF1-0E777324C082}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81DB65B3-EA65-4947-8CF1-0E777324C082}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{AA6BE73F-1E15-49A5-AC3C-CD069035C940} = {12312384-8828-4786-AE19-EFCEDF968290}
{17AABD18-E275-4409-9E33-3D755B809FF6} = {12312384-8828-4786-AE19-EFCEDF968290}
{196AA5C7-AE8A-477B-B01A-B94676EC60EE} = {12312384-8828-4786-AE19-EFCEDF968290}
{81DB65B3-EA65-4947-8CF1-0E777324C082} = {461FE3E2-21C4-47F9-8405-DF72326AAB2B}
EndGlobalSection
EndGlobal
42 changes: 0 additions & 42 deletions src/Bonsai.ML.sln

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<EnableUnsafeBinaryFormatterSerialization>true</EnableUnsafeBinaryFormatterSerialization>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Bonsai.System" Version="2.8.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<Using Include="Microsoft.VisualStudio.TestTools.UnitTesting" />
</ItemGroup>
<ItemGroup>
<Content Include="ReceptiveFieldSimpleCell/*" Exclude="ReceptiveFieldSimpleCell/*.cs">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Bonsai.ML.LinearDynamicalSystems\Bonsai.ML.LinearDynamicalSystems.csproj" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using System;
using System.IO.Compression;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Reflection;
using Newtonsoft.Json;

namespace Bonsai.ML.LinearDynamicalSystems.Tests.ReceptiveFieldSimpleCell;

[TestClass]
public class ReceptiveFieldSimpleCellTest
{
private string basePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ReceptiveFieldSimpleCell");
private int nSamples = 10000;

private void RunProcess(string fileName, string fmtArg)
{
var start = new ProcessStartInfo
{
FileName = fileName,
Arguments = fmtArg,
RedirectStandardOutput = true,
RedirectStandardInput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
};

using (var process = new Process {StartInfo = start})
{
process.Start();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();

if (!string.IsNullOrEmpty(output))
{
Console.WriteLine("Standard Output: ");
Console.WriteLine(output);
}

if (!string.IsNullOrEmpty(error))
{
Console.WriteLine("Standard Error: ");
Console.WriteLine(error);
}
}
}

private void DownloadData(string basePath)
{
string zipFileUrl = "https://zenodo.org/records/10879253/files/ReceptiveFieldSimpleCell.zip";
string outputPath = Path.Combine(basePath, "data");
string tempFilePath = Path.Combine(Path.GetTempPath(), "tempfile.zip");

try
{
using (var httpClient = new HttpClient())
{
var responseBytes = httpClient.GetByteArrayAsync(zipFileUrl).Result;
File.WriteAllBytes(tempFilePath, responseBytes);
Console.WriteLine("File downloaded successfully.");
}

ZipFile.ExtractToDirectory(tempFilePath, outputPath, true);
Console.WriteLine("File extracted successfully.");
}

catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
}

finally
{
if (File.Exists(tempFilePath))
{
File.Delete(tempFilePath);
Console.WriteLine("Temporary file deleted.");
}
}
}

private void RunPythonScript(string basePath)
{
var pythonExec = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? "python"
: "python3";
var scriptPath = Path.Combine(basePath, "run_python_test.py");
RunProcess(pythonExec, $"\"{scriptPath}\" {basePath} {nSamples}");

Console.WriteLine("Run python script finished.");
}

private async Task RunBonsaiWorkflow(string basePath)
{
var currentDirectory = Environment.CurrentDirectory;
Environment.CurrentDirectory = basePath;
try
{
var workflowPath = Path.Combine(basePath, "receptive_field.bonsai");
await WorkflowHelper.RunWorkflow(
workflowPath,
properties: [("NSamples", nSamples)]);
Console.WriteLine("Run bonsai workflow finished.");
}
finally { Environment.CurrentDirectory = currentDirectory; }
}

private string GetJsonData(string jsonFileName)
{
string jsonString = File.ReadAllText(jsonFileName);
State state = JsonConvert.DeserializeObject<State>(jsonString);

Check warning on line 113 in tests/Bonsai.ML.LinearDynamicalSystems.Tests/ReceptiveFieldSimpleCell/ReceptiveFieldSimpleCellTest.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
string jsonState = JsonConvert.SerializeObject(state);
return jsonState;
}

private State GetStateFromJson(string jsonFileName)
{
string jsonString = File.ReadAllText(jsonFileName);
State state = JsonConvert.DeserializeObject<State>(jsonString);

Check warning on line 121 in tests/Bonsai.ML.LinearDynamicalSystems.Tests/ReceptiveFieldSimpleCell/ReceptiveFieldSimpleCellTest.cs

View workflow job for this annotation

GitHub Actions / build

Converting null literal or possible null value to non-nullable type.
return state;

Check warning on line 122 in tests/Bonsai.ML.LinearDynamicalSystems.Tests/ReceptiveFieldSimpleCell/ReceptiveFieldSimpleCellTest.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference return.
}

private bool CompareJSONData(string basePath, double tolerance = 1e-9)
{
var originalFileName = Path.Combine(basePath, "original-receptivefield.json");
var bonsaiFileName = Path.Combine(basePath, "bonsai-receptivefield.json");
var pythonFileName = Path.Combine(basePath, "python-receptivefield.json");

var originalOutput = GetStateFromJson(originalFileName);
var bonsaiOutput = GetStateFromJson(bonsaiFileName);
var pythonOutput = GetStateFromJson(pythonFileName);

try
{
for (int i = 0; i < bonsaiOutput.X.GetLength(0); i++)
{
for (int j = 0; j < bonsaiOutput.X.GetLength(1); j++)
{
if (Math.Abs(bonsaiOutput.X[i,j] - pythonOutput.X[i,j]) > tolerance || Math.Abs(originalOutput.X[i,j] - pythonOutput.X[i,j]) > tolerance)
{
Console.WriteLine($"Discrepency found comparing X at index ({i},{j}) with tolerance {tolerance}: bonsaiOutput = {bonsaiOutput.X[i,j]}, pythonOutput = {pythonOutput.X[i,j]}, originalOutput = {originalOutput.X[i,j]}.");
return false;
}
}
}
for (int i = 0; i < bonsaiOutput.P.GetLength(0); i++)
{
for (int j = 0; j < bonsaiOutput.P.GetLength(1); j++)
{
if (Math.Abs(bonsaiOutput.P[i,j] - pythonOutput.P[i,j]) > tolerance || Math.Abs(originalOutput.P[i,j] - pythonOutput.P[i,j]) > tolerance)
{
Console.WriteLine($"Discrepency found comparing P at index ({i},{j}) with tolerance {tolerance}: bonsaiOutput = {bonsaiOutput.P[i,j]}, pythonOutput = {pythonOutput.P[i,j]}, originalOutput = {originalOutput.P[i,j]}.");
return false;
}
}
}
}
catch
{
return false;
}
return true;
}

[TestInitialize]
[DeploymentItem("run_python_test.py")]
[DeploymentItem("receptive_field.py")]
[DeploymentItem("receptive_field.bonsai")]
[DeploymentItem("original-receptivefield.json")]
public async Task TestSetup()
{
Directory.CreateDirectory(basePath);
DownloadData(basePath);
RunPythonScript(basePath);
await RunBonsaiWorkflow(basePath);
}

[TestMethod]
public void CompareResults()
{
var result = CompareJSONData(basePath);
Assert.IsTrue(result);
}

[TestCleanup]
public void Cleanup()
{
if (Directory.Exists(basePath))
{
Directory.Delete(basePath, true);
}
}
}

Large diffs are not rendered by default.

Loading

0 comments on commit cea88fc

Please sign in to comment.