From f427583ec497f80aa2f80ac4d34dc44b7e23643f Mon Sep 17 00:00:00 2001 From: Jack <66967891+ASpoonPlaysGames@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:52:53 +0100 Subject: Add initial unit testing logic (#708) Adds initial logic to allow for some form of basic unit testing. --- Northstar.Custom/mod.json | 13 + Northstar.Custom/mod/scripts/vscripts/_testing.nut | 302 +++++++++++++++++++++ 2 files changed, 315 insertions(+) create mode 100644 Northstar.Custom/mod/scripts/vscripts/_testing.nut (limited to 'Northstar.Custom') diff --git a/Northstar.Custom/mod.json b/Northstar.Custom/mod.json index 399311e43..69432f498 100644 --- a/Northstar.Custom/mod.json +++ b/Northstar.Custom/mod.json @@ -440,6 +440,19 @@ "Path": "sh_northstar_safe_io.gnut", "RunOn": "CLIENT || SERVER || UI" }, + { + "Path": "_testing.nut", + "RunOn": "CLIENT || SERVER || UI", + "ClientCallback": { + "Before": "Testing_Init" + }, + "ServerCallback": { + "Before": "Testing_Init" + }, + "UICallback": { + "Before": "Testing_Init" + } + }, { "Path": "_event_models.gnut", "RunOn": "SERVER && LOBBY", diff --git a/Northstar.Custom/mod/scripts/vscripts/_testing.nut b/Northstar.Custom/mod/scripts/vscripts/_testing.nut new file mode 100644 index 000000000..15bcf18ba --- /dev/null +++ b/Northstar.Custom/mod/scripts/vscripts/_testing.nut @@ -0,0 +1,302 @@ +global function Testing_Init +global function RunAllTests +global function RunAllTests_SaveToFile +global function RunTestsByCategory +global function RunTestByCategoryAndName + +global function AddTest + +struct TestInfo +{ + string testName + var functionref() callback + // whether the test completed successfully + // if this is true, actualResult is valid + bool completed + // var not string because then i can just set it to an exception + // which print can then handle + var error + // whether the test is considered successful + var expectedResult + var actualResult + bool passed +} + +struct { + table< string, array< TestInfo > > tests = {} +} file + +void function Testing_Init() +{ + // tests for the testing functions :) + //AddTest( "Example Tests", "example succeeding test", ExampleTest_ReturnsTrue, true ) + //AddTest( "Example Tests", "example failing test", ExampleTest_ReturnsFalse, true ) + //AddTest( "Example Tests", "example erroring test", ExampleTest_ThrowsError, true ) + //AddTest( "Example Tests", "example test with args", var function() { + // return ExampleTest_HasArgs_ReturnsNonVar( 2, 3 ) + //}, 6 ) +} + +int function ExampleTest_HasArgs_ReturnsNonVar( int first, int second ) +{ + return first * second +} + +var function ExampleTest_ReturnsFalse() +{ + return false +} + +var function ExampleTest_ReturnsTrue() +{ + return true +} + +var function ExampleTest_ThrowsError() +{ + throw "Example exception" + return null +} + +void function RunAllTests_SaveToFile() +{ + RunAllTests() + + #if UI + string fileName = "ns-unit-tests-UI.json" + #elseif CLIENT + string fileName = "ns-unit-tests-CLIENT.json" + #elseif SERVER + string fileName = "ns-unit-tests-SERVER.json" + #endif + + // cant encode structs so have to reconstruct a table manually from the structs + table out = {} + foreach ( category, tests in file.tests ) + { + array categoryResults = [] + foreach ( test in tests ) + { + table testTable = {} + testTable[ "name" ] <- test.testName + testTable[ "completed" ] <- test.completed + testTable[ "passed" ] <- test.passed + if ( !test.completed ) + testTable[ "error" ] <- test.error + else if ( !test.passed ) + { + testTable[ "expectedResult" ] <- test.expectedResult + testTable[ "actualResult" ] <- test.actualResult + } + + categoryResults.append( testTable ) + } + out[ category ] <- categoryResults + } + + NSSaveJSONFile( fileName, out ) +} + +void function RunAllTests() +{ + printt( "Running all tests!" ) + + foreach ( category, categoryTests in file.tests ) + { + foreach ( test in categoryTests ) + { + RunTest( test ) + } + } + + PrintAllTestResults() +} + +void function RunTestsByCategory( string category ) +{ + if ( !( category in file.tests ) ) + { + printt( format( "Category '%s' has no tests registered", category ) ) + return + } + + foreach ( categoryTest in file.tests[ category ] ) + { + RunTest( categoryTest ) + } +} + +void function RunTestByCategoryAndName( string category, string testName ) +{ + // find test + if ( !( category in file.tests ) ) + { + printt( format( "Category '%s' has no tests registered", category ) ) + return + } + + TestInfo ornull foundTest = null + foreach ( categoryTest in file.tests[ category ] ) + { + if ( categoryTest.testName == testName ) + { + foundTest = categoryTest + break + } + } + + if ( !foundTest ) + { + printt( format( "Category '%s' does not contain a test with name '%s'", category, testName ) ) + return + } + + expect TestInfo( foundTest ) + + printt( "Running test!" ) + // run test + RunTest( foundTest ) + // print result + PrintTestResult( foundTest ) +} + +void function RunTest( TestInfo test ) +{ + test.completed = false + test.passed = false + test.actualResult = null + test.error = "" + + try + { + test.actualResult = test.callback() + test.completed = true + test.passed = test.actualResult == test.expectedResult + } + catch ( exception ) + { + test.completed = false + test.error = exception + } +} + +void function PrintAllTestResults() +{ + int totalSucceeded = 0 + int totalFailed = 0 + int totalErrored = 0 + + foreach ( category, categoryTests in file.tests ) + { + int categorySucceeded = 0 + int categoryFailed = 0 + int categoryErrored = 0 + + printt( format( "Results for category: '%s'", category ) ) + foreach ( test in categoryTests ) + { + if ( test.completed ) + { + if ( test.passed ) + { + printt( "\t", test.testName, "- Passed!" ) + categorySucceeded++ + } + else + { + printt( "\t", test.testName, "- Failed!" ) + printt( "\t\tExpected:", test.expectedResult ) + printt( "\t\tActual: ", test.actualResult ) + categoryFailed++ + } + } + else + { + printt( "\t", test.testName, "- Errored!" ) + printt( "\t\tError:", test.error ) + categoryErrored++ + } + } + + printt( "Succeeded:", categorySucceeded, "Failed:", categoryFailed, "Errored:", categoryErrored ) + + totalSucceeded += categorySucceeded + totalFailed += categoryFailed + totalErrored += categoryErrored + } + + printt( "TOTAL SUCCEEDED:", totalSucceeded, "TOTAL FAILED:", totalFailed, "TOTAL ERRORED:", totalErrored ) +} + +void function PrintCategoryResults( string category ) +{ + int categorySucceeded = 0 + int categoryFailed = 0 + int categoryErrored = 0 + + printt( format( "Results for category: '%s'", category ) ) + foreach ( test in file.tests[ category ] ) + { + if ( test.completed ) + { + if ( test.passed ) + { + printt( "\t", test.testName, "- Passed!" ) + categorySucceeded++ + } + else + { + printt( "\t", test.testName, "- Failed!" ) + printt( "\t\tExpected:", test.expectedResult ) + printt( "\t\tActual: ", test.actualResult ) + categoryFailed++ + } + } + else + { + printt( "\t", test.testName, "- Errored!" ) + printt( "\t\tError:", test.error ) + categoryErrored++ + } + } + + printt( "Succeeded:", categorySucceeded, "Failed:", categoryFailed, "Errored:", categoryErrored ) +} + +void function PrintTestResult( TestInfo test ) +{ + string resultString = test.testName + + if ( test.completed ) + { + if ( test.passed ) + resultString += " - Passed!" + else + { + resultString += " - Failed!" + resultString += "\n\tExpected: " + test.expectedResult + resultString += "\n\tActual: " + test.actualResult + } + } + else + { + resultString += " - Not completed!" + resultString += "\n\tError: " + test.error + } + + printt( resultString ) +} + +void function AddTest( string testCategory, string testName, var functionref() testFunc, var expectedResult ) +{ + TestInfo newTest + newTest.testName = testName + newTest.callback = testFunc + newTest.expectedResult = expectedResult + + // create the test category if it doesn't exist + if ( !( testCategory in file.tests ) ) + file.tests[ testCategory ] <- [ newTest ] + else + file.tests[ testCategory ].append( newTest ) +} -- cgit v1.2.3