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

Prettify the stdlib testing framework #542

Merged
merged 14 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion effekt/jvm/src/test/scala/effekt/StdlibTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ class StdlibMLTests extends StdlibTests {
// unicode is not supported in the ML backend
examplesDir / "stdlib" / "string" / "unicode.effekt",

// missing support for multibyte character escape in a string
examplesDir / "stdlib" / "test",

Comment on lines +48 to +50
Copy link
Contributor Author

@jiribenes jiribenes Sep 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would need #544, but we can fix that later, I'm fine with MLton support being rather second-class. :)

// Not implemented yet
examplesDir / "stdlib" / "bytes",
examplesDir / "stdlib" / "io"
examplesDir / "stdlib" / "io",
)
}
class StdlibLLVMTests extends StdlibTests {
Expand Down
6 changes: 0 additions & 6 deletions examples/pos/lib_test.check

This file was deleted.

9 changes: 9 additions & 0 deletions examples/stdlib/test/fail.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Arithmetic
✓ addition
✕ faulty addition
2 + 2 should be 4!
✓ multiplication

2 pass
1 fail
3 tests total
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import test

def main() = {

suite("Arithmetic") {
// Don't print out times in CI.
suite("Arithmetic", false) {

test("addition") {
val x = 1;
Expand Down
8 changes: 8 additions & 0 deletions examples/stdlib/test/pass.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Test test suite
✓ Test test assertTrue
✓ Test test assertFalse
✓ Test test assert

3 pass
0 fail
3 tests total
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import test

def main() = {
suite("Test test suite") {
// Don't print out times in CI.
suite("Test test suite", false) {
test("Test test assertTrue") {
assertTrue(true)
assertTrue(true, "true")
Expand Down
5 changes: 0 additions & 5 deletions examples/stdlib/test/test.check

This file was deleted.

36 changes: 32 additions & 4 deletions libraries/common/bench.effekt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module bench

type Nanos = Int

/**
* The current time (since UNIX Epoch) in nanoseconds.
*
Expand All @@ -8,7 +10,7 @@ module bench
* - chez: Microseconds
* - ml: Microseconds
*/
extern io def timestamp(): Int =
extern io def timestamp(): Nanos =
js "Date.now() * 1000000"
chez "(timestamp)"
ml "IntInf.toInt (Time.toNanoseconds (Time.now ()))"
Expand All @@ -27,24 +29,35 @@ extern llvm """
declare i32 @clock_gettime(i32, ptr)
"""

// This should not be needed when using NodeJS 16.x or newer.
extern jsNode """
const { performance } = require('node:perf_hooks');
"""

/**
* High-precision timestamp in nanoseconds that should be for measurements.
*
* This timestamp should only be used for **relative** measurements,
* as gives no guarantees on the absolute time (unlike a UNIX timestamp).
*/
extern io def relativeTimestamp(): Int =
extern io def relativeTimestamp(): Nanos =
js "Math.round(performance.now() * 1000000)"
default { timestamp() }

type Duration = Int

namespace Duration {
def diff(fromNanos: Nanos, toNanos: Nanos): Duration = toNanos - fromNanos
}

/**
* Runs the block and returns the time in nanoseconds
*/
def timed { block: => Unit }: Int = {
def timed { block: => Unit }: Duration = {
val before = relativeTimestamp()
block()
val after = relativeTimestamp()
after - before
Duration::diff(before, after)
}

def measure(warmup: Int, iterations: Int) { block: => Unit }: Unit = {
Expand All @@ -58,3 +71,18 @@ def measure(warmup: Int, iterations: Int) { block: => Unit }: Unit = {
run(warmup, false)
run(iterations, true)
}

/**
* Takes a duration in nanoseconds and formats it in milliseconds with precision of two decimal digits.
*/
def formatMs(nanos: Nanos): String = {
val micros = nanos / 1000000
val sub = (nanos.mod(1000000).toDouble / 10000.0).round

if (sub == 100) {
// overflow because of rounding
(micros + 1).show ++ ".0ms"
} else {
micros.show ++ "." ++ sub.show ++ "ms"
}
}
24 changes: 0 additions & 24 deletions libraries/common/string.effekt
Original file line number Diff line number Diff line change
Expand Up @@ -243,30 +243,6 @@ def string[T] { prog: => T / Stream }: (T, String) = <>
def printing[T] { prog: => T / Stream }: T = <>


// ANSI escape codes
namespace ANSI {
val BLACK = "\u001b[30m"
val RED = "\u001b[31m"
val GREEN = "\u001b[32m"
val YELLOW = "\u001b[33m"
val BLUE = "\u001b[34m"
val MAGENTA = "\u001b[35m"
val CYAN = "\u001b[36m"
val WHITE = "\u001b[37m"

val BG_BLACK = "\u001b[40m"
val BG_RED = "\u001b[41m"
val BG_GREEN = "\u001b[42m"
val BG_YELLOW = "\u001b[43m"
val BG_BLUE = "\u001b[44m"
val BG_MAGENTA = "\u001b[45m"
val BG_CYAN = "\u001b[46m"
val BG_WHITE = "\u001b[47m"

val RESET = "\u001b[0m"
}


// Characters
// ----------
//
Expand Down
66 changes: 66 additions & 0 deletions libraries/common/string/tty.effekt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
module string/tty

// ANSI escape codes
namespace ANSI {
val CSI = "\u001b["

def escape(s: String) = CSI ++ s ++ "m"

val BLACK = escape("30")
val RED = escape("31")
val GREEN = escape("32")
val YELLOW = escape("33")
val BLUE = escape("34")
val MAGENTA = escape("35")
val CYAN = escape("36")
val WHITE = escape("37")

val BG_BLACK = escape("40")
val BG_RED = escape("41")
val BG_GREEN = escape("42")
val BG_YELLOW = escape("43")
val BG_BLUE = escape("44")
val BG_MAGENTA = escape("45")
val BG_CYAN = escape("46")
val BG_WHITE = escape("47")

val RESET = escape("0")

val BOLD = escape("1")
val FAINT = escape("2")
val ITALIC = escape("3")
val UNDERLINE = escape("4")
val BLINK = escape("5")
val REVERSE = escape("7")
val CROSSOUT = escape("9")
val OVERLINE = escape("53")
}

def red(text: String) = Formatted::colored(text, ANSI::RED)
def green(text: String) = Formatted::colored(text, ANSI::GREEN)
def dim(text: String) = Formatted::colored(text, ANSI::FAINT)
def bold(text: String) = Formatted::colored(text, ANSI::BOLD)

interface Formatted {
def supportsEscape(escape: String): Bool
}

namespace Formatted {
/// Run given block of code, allowing all formatting
def formatting[R] { prog : => R / Formatted }: R =
try { prog() } with Formatted {
def supportsEscape(escape: String) = resume(true)
}

/// Run given block of code, ignoring all formatting
def noFormatting[R] { prog : => R / Formatted }: R =
try { prog() } with Formatted {
def supportsEscape(escape: String) = resume(false)
}

def tryEmit(escape: String): String / Formatted =
if (do supportsEscape(escape)) escape else ""

def colored(text: String, colorEscape: String): String / Formatted =
tryEmit(colorEscape) ++ text ++ tryEmit(ANSI::RESET)
}
Loading