Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests #501

Open
kengorab opened this issue Nov 14, 2024 · 0 comments
Open

Tests #501

kengorab opened this issue Nov 14, 2024 · 0 comments

Comments

@kengorab
Copy link
Owner

kengorab commented Nov 14, 2024

There should be a builtin mechanism for writing tests and assertions. There could be a std package called "testing" which would expose typical testing utilities (expectations, test runners, etc). Here's an example test:

// json.test.abra
import "json" as json
import T, expect from "testing"

@Test
func parsingBoolean(t: T) {
  val trueVal = expectResult(t, json.JsonParser.parseString("true")).toBeOk()
  expect(t, trueVal).toEqual(json.JsonValue.Boolean(true))

  val falseVal = expectResult(t, json.JsonParser.parseString("false")).toBeOk()
  expect(t, falseVal).toEqual(json.JsonValue.Boolean(false))
}

The Test decorator could look like this (using as-of-yet undefined syntax for declaring a decorator):

export decorator Test {
  displayName: String = ""
  skip: Bool = false
}

The std/testing.abra module could look like

export type T {
  _message: String = ""

  @NoReturn
  func fail(self, message: String) {
    self._message = message

    libc.longjmp()
  }
}

export decorator Test {
  displayName: String = ""
  skip: Bool = false
}

export enum TestResult {
  NotStarted
  Skipped
  Passed
  Failed(reason: String)
}

export type TestContext {
  name: String
  file: String
  skip: Bool
  fn: (T) => Unit
  result: TestResult.NotStarted
}

val _tests: TestContext[] = []

export func addTest(file: String, name: String, skip: Boolean, fn: (T) => Unit) {
  val test = TestContext(name: name, file: file, skip: skip, fn: fn)
  _tests.push(test)
}

export func runTests() {
  for test in _tests {
    if test.skip {
      test.result = TestResult.Skipped
      continue
    }

    val t = T()
    if libc.setjmp() {
      test.result = TestResult.Failed(t._message)
    } else {
      test.fn(t)
      test.result = TestResult.Passed
    }
  }

  printTestResults(_tests)
}

func printTestResults(tests: TestContext[]) {
  // ...
}

export func expect<V>(t: T, value: V): ExpectHarness<V> = ExpectHarness(t: t, value: value)

export type ExpectHarness<V> {
  t: T
  value: V

  func toEqual(self, expected: V) {
    if self.value != expected {
      self.t.fail("expected '$expected' but got '${self.value}'")
    }
  }
}

export func expectResult<V, E>(t: T, value: Result<V, E>): ExpectResultHarness<V> = ExpectResultHarness(t: t, value: value)

export type ExpectResultHarness<V, E> {
  t: T
  value: Result<V, E>

  func toEqual(self, expected: Result<V, E>) {
    if self.value != expected {
      self.t.fail("expected '$expected' but got '${self.value}'")
    }
  }

  func toBeOk(self): V {
    match self.value {
      Ok(v) => v
      Err(e) => {
        self.t.fail("expected Result value to be Ok, but was Err($e)")
      }
    }
  }

  func toBeErr(self): E {
    match self.value {
      Err(e) => e
      Ok(v) => {
        self.t.fail("expected Result value to be Err, but was Ok($v)")
      }
    }
  }
}

export func expectOption<V>(t: T, value: V?): ExpectOptionHarness<V> = ExpectOptionHarness(t: t, value: value)

export type ExpectOptionHarness<V> {
  t: T
  value: V?

  func toEqual(self, expected: V?) {
    if self.value != expected {
      self.t.fail("expected '$expected' but got '${self.value}'")
    }
  }

  func toBeSome(self): V {
    if self.value |v| v else {
      self.t.fail("expected Option value to be Some, but was None")
    }
  }

  func toBeNone(self) {
    if self.value |v| v {
      self.t.fail("expected Option value to be None, but was Some($v)")
    }
  }
}

The basic strategy for the abra test cli command would be like:

  1. find all *.test.abra files in cwd (recursively)
  2. create a pseudo module that looks something like this
import "....file1.test.abra"
import "....file2.test.abra"
...

import "testing" as ___testing___ // use a guaranteed unique name

___testing___.runTests()
...
  1. add a flag to the typechecker which signifies "testMode"
    this flag checks for functions decorated with @Test and:

    • verifies they have exactly 1 param (of type "testing".T)
    • verifies that they're standalone functions
    • emits a call to "testing"._addTest(...) after it
  2. add a call to "testing".runTests()

  3. compile the module and run the executable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant