If you have Docker and just want to quickly try out Vary, use our Docker image. Everything is pre-installed. The container deletes itself when you exit, so nothing touches your machine. Once you feel comfortable, you can install Vary properly on Linux, macOS, or Windows.
This walkthrough takes about five minutes. By the end you will have run a Vary program, executed tests, and seen mutation testing find a gap in a test suite.
Pull the image and drop into a shell:
docker run -it --rm --hostname vary --entrypoint bash ghcr.io/ccollicutt/vary:latest
The --entrypoint bash overrides the default entrypoint (which is the vary command) to give you a shell. The --rm flag removes the container when you exit. The Vary compiler, test runner, formatter, mutation engine, and REPL are all pre-installed.
Verify it works:
vary@vary:/workspace$ vary --version
Vary v113-alpha.1
The image ships with example programs in /opt/vary/examples/. List them:
vary@vary:/workspace$ ls /opt/vary/examples/
cli_demo.vary enums.vary fibonacci.vary game_of_life.vary math_utils.vary ...
counter.vary factorial.vary fizzbuzz.vary hello.vary tuples.vary ...
Run the simplest one:
vary@vary:/workspace$ vary run /opt/vary/examples/hello.vary
Hello, World!
That is the entire program. Vary runs top-level code directly. No main function, no boilerplate.
vary@vary:/workspace$ vary run /opt/vary/examples/fizzbuzz.vary
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
This prints the classic FizzBuzz sequence from 1 to 100. The source is 13 lines of straightforward code:
mut i = 1
while i <= 100 {
if i % 15 == 0 {
print("FizzBuzz")
} elif i % 3 == 0 {
print("Fizz")
} elif i % 5 == 0 {
print("Buzz")
} else {
print(i)
}
i = i + 1
}
If you have written Python, this should look familiar. The differences: braces instead of indentation, explicit types on function parameters, and mut for mutable variables.
Vary has a built-in test runner. The examples include test files for the Fibonacci module:
vary@vary:/workspace$ vary test /opt/vary/examples/fibonacci_test.vary
Found 1 test file(s)
fibonacci_test.vary:
PASS: fib base cases
PASS: fib sequence
PASS: fib larger
==================================================
Results: 3 passed, 0 failed
Look at what the test file contains:
from fibonacci import fib
test "fib base cases" {
observe fib(0) == 0
observe fib(1) == 1
}
test "fib sequence" {
observe fib(2) == 1
observe fib(3) == 2
observe fib(4) == 3
observe fib(5) == 5
observe fib(6) == 8
}
test "fib larger" {
observe fib(10) == 55
}
Tests are test "name" { } blocks with observe assertions. No test framework to install, no imports to remember. The compiler knows what tests are.
This is where things get interesting. Tests tell you the code works. Mutation testing tells you the tests are actually checking something.
Run mutation testing against the math utils example:
vary@vary:/workspace$ vary mutate /opt/vary/examples/math_utils.vary
The mutation engine takes the math_utils.vary source, makes small changes to it (swapping + for -, changing * to +, replacing return values), and reruns the tests for each change. If the tests still pass after a change, that mutant "survived," which means the tests have a gap.
The output shows a score: the percentage of mutants your tests caught. A function like square that returns x * x might have a surviving mutant where * becomes +, but only if the test does not check values where x * x differs from x + x (which is every value except 0 and 2).
You can ask the engine to explain a specific survivor:
vary@vary:/workspace$ vary mutate /opt/vary/examples/math_utils.vary --why
This prints what changed, where, and what test you could write to catch it.
Create a file right in the container:
vary@vary:/workspace$ cat > /tmp/greet.vary << 'EOF'
def greet(name: Str) -> Str {
return f"Hello, {name}!"
}
print(greet("Docker"))
print(greet("Vary"))
EOF
Run it:
vary@vary:/workspace$ vary run /tmp/greet.vary
Hello, Docker!
Hello, Vary!
Types are checked at compile time. Try changing the function to return an Int instead of Str and the compiler will catch it before anything runs.
For quick experiments, Vary has an interactive mode:
vary@vary:/workspace$ vary repl
>>> 2 ** 10
1024
>>> let name = "Vary"
>>> f"Hello, {name}!"
Hello, Vary!
>>> exit()
Type exit to leave the container. Because you used --rm, Docker deletes the container and everything in it. Your host machine is untouched.
vary@vary:/workspace$ exit
If you liked what you saw and want to install Vary on your machine, see the platform guides for Linux, macOS, or Windows. For a persistent Docker setup with volume mounts instead of a throwaway container, see the Docker guide.
Once installed, the getting started page walks through the language in more depth. The test DSL page covers writing tests, and the mutation testing page explains the mutation engine in full.