def printTestResult:
  . as $result | $result |
  if .passed then
    "Test \"\(.name)\" Passed"
  else 
    "Test \"\(.name)\" Failed\nReason: \(.reason)\nOutput: \(.output | tojson)"
  end;

def runTest($name; testExpr; checkResult; $expectError):
  try (
    (null | testExpr) as $output |
    if $expectError then
      {
        $name,
        passed: false,
        reason: "Expected error but no error was raised",
        $output
      }
    elif ($output | checkResult) then
      {
        $name,
        passed: true,
        $output
      }
    else 
      {
        $name,
        passed: false,
        reason: "Result was different from expected",
        $output
      }
    end
  ) catch (
    . as $error |
    if ($expectError | not) then
      {
        $name,
        passed: false,
        reason: "Error caught when no error was expected",
        output: $error
      }
    elif ($expectError and ($error | checkResult)) then
      {
        $name,
        passed: true,
        output: $error
      }
    elif ($expectError and ($error | checkResult | not)) then
      {
        $name,
        passed: false,
        reason: "Expected error but received different error",
        output: $error
      }
    else 
      error("unknown error")
    end
  );

def expectPassed:
  if (.passed | not) then
    error(. | printTestResult)
  end;
def expectPassed($result): $result | expectPassed;

def expectFailed:
  if (.passed) then
    error(. | printTestResult)
  end;
def expectFailed($result): $result | expectFailed;

def testTests:
  expectPassed(runTest(
    "passing test";
    true;
    . == true;
    false
  )) |
  expectPassed(runTest(
    "error expected and equal";
    error("error");
    . == "error";
    true
  )) |
  expectFailed(runTest(
    "failing test";
    true;
    . == false;
    false
  )) |
  expectFailed(runTest(
    "error expected but no error";
    null;
    . == null;
    true
  )) |
  expectFailed(runTest(
    "error not expected";
    error("error");
    . == null;
    false
  )) |
  expectFailed(runTest(
    "error different";
    error("error");
    . == "different error";
    true
  ));

def testLibMain:
  testTests |
  "Tests Passed\n" |
  halt_error(0);