Skip to content

Commit

Permalink
feat: process errors
Browse files Browse the repository at this point in the history
  • Loading branch information
yongenaelf committed Sep 13, 2024
1 parent 887b3eb commit 40b1c4c
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 35 deletions.
68 changes: 49 additions & 19 deletions components/workspace/format-errors.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { badgeVariants, Badge } from "@/components/ui/badge";
import Link from "next/link";
import { processTestOutput } from "./process-test-output";

export function FormatErrors({ inputString }: { inputString: string }) {
// Detect and remove the dynamic path
Expand All @@ -13,25 +14,54 @@ export function FormatErrors({ inputString }: { inputString: string }) {
/(\w+\/[^\n]*): (error|warning) [^\n]*/g
);

// Print each error or warning message on a new line
if (errorMessages) {
return (
<table>
<thead>
<tr>
<th className="p-2">Path</th>
<th className="p-2">Type</th>
<th className="p-2">Description</th>
</tr>
</thead>
{errorMessages.map((m) => (
<ErrorMessage key={m} message={m} />
))}
</table>
);
} else {
return <p>No errors or warnings.</p>;
}
const testResults = processTestOutput(inputString);

return (
<>
{errorMessages ? (
<table>
<thead>
<tr>
<th className="p-2">Path</th>
<th className="p-2">Type</th>
<th className="p-2">Description</th>
</tr>
</thead>
{errorMessages.map((m) => (
<ErrorMessage key={m} message={m} />
))}
</table>
) : null}
{testResults.length > 0 ? (
<table>
<thead>
<tr>
<th className="p-2">Status</th>
<th className="p-2">Name</th>
<th className="p-2">Duration (ms)</th>
</tr>
</thead>
{testResults.map((test, key) => (
<TestResult key={`${test.name}-${key}`} test={test} />
))}
</table>
) : null}
</>
);
}

function TestResult({ test }: { test: { status: string; name: string; duration: number, message?: string } }) {
return (
<tr className="border border-black">
<td className="p-2">
<Badge variant={test.status === "passed" ? "default" : "destructive"}>
{test.status}
</Badge>
</td>
<td className="p-2">{test.name}<br />{test?.message}</td>
<td className="p-2">{test.duration}</td>
</tr>
);
}

function ErrorMessage({ message }: { message: string }) {
Expand Down
45 changes: 30 additions & 15 deletions components/workspace/process-test-output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ describe('processTestOutput', () => {
it('should return an array of test results for a valid test output string', () => {
const result = processTestOutput(testString);
expect(result).toEqual([
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_Empty', duration: 5000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.HasVoted_ProposalNotFound_ShouldThrow', duration: 2000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_MultipleUsers_ShouldSucceed', duration: 2000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_ProposalNotFound_ShouldThrow', duration: 1000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_Success_Reject', duration: 838 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.CreateProposal_InvalidStartTime_ShouldThrow', duration: 835 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_Success_Approve', duration: 1000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_ProposalsWithDifferentStates', duration: 715 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Withdraw_ProposalNotFound_ShouldThrow', duration: 1000 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetProposal_Success', duration: 867 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_MultipleProposals', duration: 606 },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.InitializeContract_Fail_AlreadyInitialized', duration: 848 }
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_Empty', duration: 5000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.HasVoted_ProposalNotFound_ShouldThrow', duration: 2000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_MultipleUsers_ShouldSucceed', duration: 2000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_ProposalNotFound_ShouldThrow', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_Success_Reject', duration: 838, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.CreateProposal_InvalidStartTime_ShouldThrow', duration: 835, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Vote_Success_Approve', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_ProposalsWithDifferentStates', duration: 715, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.Withdraw_ProposalNotFound_ShouldThrow', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetProposal_Success', duration: 867, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_MultipleProposals', duration: 606, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.InitializeContract_Fail_AlreadyInitialized', duration: 848, message: undefined }
]);
});

Expand All @@ -37,10 +37,25 @@ describe('processTestOutput', () => {
const mixedResultsString = `Test run for /tmp/playground/08993f10-e0ef-4bbf-b6e0-5a5ce4fa71e3/test/bin/Debug/net6.0/MyWayDAO.Tests.dll (.NETCoreApp,Version=v6.0)\nMicrosoft (R) Test Execution Command Line Tool Version 17.3.3 (x64)\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nStarting test execution, please wait...\nA total of 1 test files matched the specified pattern.\n/tmp/playground/08993f10-e0ef-4bbf-b6e0-5a5ce4fa71e3/test/bin/Debug/net6.0/MyWayDAO.Tests.dll\n[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.1 (64-bit .NET 6.0.18)\n[xUnit.net 00:00:03.21] Discovering: MyWayDAO.Tests\n[xUnit.net 00:00:03.27] Discovered: MyWayDAO.Tests\n[xUnit.net 00:00:03.28] Starting: MyWayDAO.Tests\n Passed AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_Empty [5 s]\n Failed AElf.Contracts.MyWayDAO.MyWayDAOTests.HasVoted_ProposalNotFound_ShouldThrow [2 s]\n\nTotal tests: Unknown\n Passed: 1\n Failed: 1\n Total time: 7 Seconds\n\nThe active test run was aborted. Reason: Test host process crashed\nTest Run Aborted with error System.Exception: One or more errors occurred.\n ---> System.Exception: Unable to read beyond the end of the stream.\n at System.IO.BinaryReader.Read7BitEncodedInt()\n at System.IO.BinaryReader.ReadString()\n at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.LengthPrefixCommunicationChannel.NotifyDataAvailable()\n at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.TcpClientExtensions.MessageLoopAsync(TcpClient client, ICommunicationChannel channel, Action\`1 errorHandler, CancellationToken cancellationToken)\n --- End of inner exception stack trace ---.\n\n`;
const result = processTestOutput(mixedResultsString);
expect(result).toEqual([
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_Empty', duration: 5000 },
{ status: 'failed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.HasVoted_ProposalNotFound_ShouldThrow', duration: 2000 }
{ status: 'passed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.GetAllProposals_Empty', duration: 5000, message: undefined },
{ status: 'failed', name: 'AElf.Contracts.MyWayDAO.MyWayDAOTests.HasVoted_ProposalNotFound_ShouldThrow', duration: 2000, message: "" }
]);
});
});

it('should show the details of the failed test', () => {
const testWithFailure = "Process exited with code 1. Details: Determining projects to restore...\n Restored /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/src/dao.csproj (in 1.28 sec).\n Restored /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/dao.Tests.csproj (in 2.66 sec).\nProtobuf/reference/acs12.proto(13,1): warning : warning: Import aelf/core.proto is unused. [/tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/src/dao.csproj]\n dao -> /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/src/bin/Debug/net6.0/dao.dll\n [CONTRACT-PATCHER] Saving as /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/src/bin/Debug/net6.0/dao.dll.patched\nProtobuf/reference/acs12.proto(13,1): warning : warning: Import aelf/core.proto is unused. [/tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/dao.Tests.csproj]\n dao.Tests -> /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/bin/Debug/net6.0/dao.Tests.dll\nTest run for /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/bin/Debug/net6.0/dao.Tests.dll (.NETCoreApp,Version=v6.0)\nMicrosoft (R) Test Execution Command Line Tool Version 17.3.3 (x64)\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nStarting test execution, please wait...\nA total of 1 test files matched the specified pattern.\n/tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/bin/Debug/net6.0/dao.Tests.dll\n[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.1 (64-bit .NET 6.0.18)\n[xUnit.net 00:00:02.35] Discovering: dao.Tests\n[xUnit.net 00:00:02.40] Discovered: dao.Tests\n[xUnit.net 00:00:02.40] Starting: dao.Tests\n Passed AElf.Contracts.dao.daoTests.GetAllProposals_ProposalsWithDifferentStates [4 s]\n Passed AElf.Contracts.dao.daoTests.GetAllProposals_MultipleProposals [1 s]\n Passed AElf.Contracts.dao.daoTests.GetProposal_Success [1 s]\n[xUnit.net 00:00:11.85] Shouldly.ShouldAssertException : exception.Message\n[xUnit.net 00:00:11.85] should contain (case insensitive comparison)\n[xUnit.net 00:00:11.85] \"Start time should be greater or equal to current block tie.\"\n[xUnit.net 00:00:11.85] but was actually\n[xUnit.net 00:00:11.85] \"Failed to execute CreateProposal. AElf.Sdk.CSharp.AssertionException: Start time should be greater o...\"\n[xUnit.net 00:00:11.85] Stack Trace:\n[xUnit.net 00:00:11.85] /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/daoTests.cs(135,0): at AElf.Contracts.dao.daoTests.CreateProposal_InvalidStartTime_ShouldThrow()\n[xUnit.net 00:00:11.85] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n[xUnit.net 00:00:11.85] --- End of stack trace from previous location ---\n[xUnit.net 00:00:11.85] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n[xUnit.net 00:00:11.85] at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n[xUnit.net 00:00:11.85] at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n Passed AElf.Contracts.dao.daoTests.InitializeContract_Fail_AlreadyInitialized [1 s]\n Failed AElf.Contracts.dao.daoTests.CreateProposal_InvalidStartTime_ShouldThrow [988 ms]\n Error Message:\n Shouldly.ShouldAssertException : exception.Message\n should contain (case insensitive comparison)\n\"Start time should be greater or equal to current block tie.\"\n but was actually\n\"Failed to execute CreateProposal. AElf.Sdk.CSharp.AssertionException: Start time should be greater o...\"\n Stack Trace:\n at AElf.Contracts.dao.daoTests.CreateProposal_InvalidStartTime_ShouldThrow() in /tmp/playground/362fc693-ea25-4197-bc72-6e7b41255edb/test/daoTests.cs:line 135\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n--- End of stack trace from previous location ---\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\n Passed AElf.Contracts.dao.daoTests.CreateProposal_EmptyDescription_ShouldThrow [1 s]\n Passed AElf.Contracts.dao.daoTests.Withdraw_ProposalNotFound_ShouldThrow [939 ms]\n Passed AElf.Contracts.dao.daoTests.Withdraw_MultipleUsersAttempting_Success [977 ms]\n Passed AElf.Contracts.dao.daoTests.GetProposal_ProposalWithVotes_ShouldSucceed [1 s]\n\nTotal tests: Unknown\n Passed: 8\n Failed: 1\n Total time: 21.6233 Seconds\n\n[xUnit.net 00:00:11.85] AElf.Contracts.dao.daoTests.CreateProposal_InvalidStartTime_ShouldThrow [FAIL]\nThe active test run was aborted. Reason: Test host process crashed\nTest Run Aborted with error System.Exception: One or more errors occurred.\n ---> System.Exception: Unable to read beyond the end of the stream.\n at System.IO.BinaryReader.Read7BitEncodedInt()\n at System.IO.BinaryReader.ReadString()\n at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.LengthPrefixCommunicationChannel.NotifyDataAvailable()\n at Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.TcpClientExtensions.MessageLoopAsync(TcpClient client, ICommunicationChannel channel, Action`1 errorHandler, CancellationToken cancellationToken)\n --- End of inner exception stack trace ---.\n\n";
const result = processTestOutput(testWithFailure);
expect(result).toEqual([
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.GetAllProposals_ProposalsWithDifferentStates', duration: 4000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.GetAllProposals_MultipleProposals', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.GetProposal_Success', duration: 1000, message: undefined },
{ status: 'failed', name: 'AElf.Contracts.dao.daoTests.CreateProposal_InvalidStartTime_ShouldThrow', duration: 988, message: "Shouldly.ShouldAssertException : exception.Message should contain (case insensitive comparison) \"Start time should be greater or equal to current block tie.\" but was actually \"Failed to execute CreateProposal. AElf.Sdk.CSharp.AssertionException: Start time should be greater o...\"" },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.InitializeContract_Fail_AlreadyInitialized', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.CreateProposal_EmptyDescription_ShouldThrow', duration: 1000, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.Withdraw_ProposalNotFound_ShouldThrow', duration: 939, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.Withdraw_MultipleUsersAttempting_Success', duration: 977, message: undefined },
{ status: 'passed', name: 'AElf.Contracts.dao.daoTests.GetProposal_ProposalWithVotes_ShouldSucceed', duration: 1000, message: undefined }
]);
});
});

18 changes: 17 additions & 1 deletion components/workspace/process-test-output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ export function processTestOutput(str: string) {
const results = [];
const lines = str.split('\n');

let lineNum = 0;

for (const line of lines) {

const status = line.match(/^\s*(Passed|Failed)\s+/)?.[1].toLowerCase();
Expand All @@ -21,8 +23,22 @@ export function processTestOutput(str: string) {
duration = parseFloat(durationStr.split(' s')[0]) * 1000;
}

results.push({name, status, duration});
let message: string | undefined;

if (status === 'failed') {
// Find the error message. It is between the next line and "Stack Trace:"

const start = lineNum + 2;
const end = lines.slice(start).findIndex(l => l.includes('Stack Trace:')) + start;
message = lines.slice(start, end).map(i => i.trim()).join(' ');

console.log(message);
}

results.push({name, status, duration, message});
}

lineNum++;
}

return results;
Expand Down

0 comments on commit 40b1c4c

Please sign in to comment.