vix.app Tests and Examples
vix.app is intentionally designed around one rule:
one vix.app = one targetThat rule also affects how tests and examples should be organized.
Instead of adding a complex multi-target syntax to one manifest, the recommended approach is to use separate manifests:
project/
vix.app
tests/
vix.app
examples/
basic/
vix.appThis keeps each target small, explicit, and easy to build.
Why tests and examples are separate
A test executable is a different target from the library or application being tested.
An example executable is also a different target.
If vix.app tried to describe everything in one file, it would quickly need concepts like:
targets
test targets
example targets
target dependencies
public includes
private includes
per-target packages
per-target links
per-target resourcesThat is already build-system territory.
For vix.app V1, the simpler model is better:
root/vix.app -> main app or library
tests/vix.app -> test executable
examples/basic/vix.app -> example executableEach folder is independently buildable.
Recommended library layout
For a library with tests and examples:
mathlib/
vix.app
include/
mathlib/
math.hpp
src/
add.cpp
mul.cpp
tests/
vix.app
test_math.cpp
examples/
basic/
vix.app
src/
main.cppThis gives three independent targets:
mathlib -> static library
mathlib_tests -> test executable
mathlib_basic_example -> example executableRoot library manifest
The root manifest builds the library.
mathlib/vix.app:
name = mathlib
type = static
standard = c++20
sources = [
src/add.cpp,
src/mul.cpp,
]
include_dirs = [
include,
]This manifest describes the library target only.
It does not describe tests.
It does not describe examples.
Library code
include/mathlib/math.hpp:
#pragma once
namespace mathlib
{
int add(int a, int b);
int mul(int a, int b);
}src/add.cpp:
#include <mathlib/math.hpp>
namespace mathlib
{
int add(int a, int b)
{
return a + b;
}
}src/mul.cpp:
#include <mathlib/math.hpp>
namespace mathlib
{
int mul(int a, int b)
{
return a * b;
}
}Build the library:
vix buildTest manifest
The test manifest builds an executable.
mathlib/tests/vix.app:
name = mathlib_tests
type = executable
standard = c++20
sources = [
test_math.cpp,
../src/add.cpp,
../src/mul.cpp,
]
include_dirs = [
../include,
]Notice the paths:
test_math.cpp
../src/add.cpp
../src/mul.cpp
../includeBecause this manifest is inside:
mathlib/tests/all paths are relative to that directory.
Test code
mathlib/tests/test_math.cpp:
#include <vix.hpp>
#include <mathlib/math.hpp>
int main()
{
if (mathlib::add(2, 3) != 5)
{
vix::print("add test failed");
return 1;
}
if (mathlib::mul(4, 5) != 20)
{
vix::print("mul test failed");
return 1;
}
vix::print("all tests passed");
return 0;
}Run the tests:
cd tests
vix runExpected output:
all tests passedWhy tests include source files again
In vix.app V1, there is no multi-target dependency graph inside one manifest.
So the test executable includes the implementation files it needs:
sources = [
test_math.cpp,
../src/add.cpp,
../src/mul.cpp,
]This is simple and explicit.
For small libraries, it works well.
For larger projects with many targets and dependencies, use CMakeLists.txt.
Application tests
Applications should keep most logic outside main.cpp.
Recommended layout:
myapp/
vix.app
include/
myapp/
app.hpp
src/
main.cpp
app.cpp
tests/
vix.app
test_app.cppRoot vix.app:
name = myapp
type = executable
standard = c++20
sources = [
src/main.cpp,
src/app.cpp,
]
include_dirs = [
include,
]Test vix.app:
name = myapp_tests
type = executable
standard = c++20
sources = [
test_app.cpp,
../src/app.cpp,
]
include_dirs = [
../include,
]The test manifest includes:
../src/app.cppbut not:
../src/main.cppThis avoids duplicate main() errors.
Thin main.cpp pattern
Keep main.cpp small:
#include <myapp/app.hpp>
int main()
{
return myapp::run();
}Put testable logic in:
src/app.cpp
include/myapp/app.hppThis makes the test target simple.
Application code example
include/myapp/app.hpp:
#pragma once
namespace myapp
{
int add(int a, int b);
int run();
}src/app.cpp:
#include <vix.hpp>
#include <myapp/app.hpp>
namespace myapp
{
int add(int a, int b)
{
return a + b;
}
int run()
{
vix::print("myapp running");
return 0;
}
}src/main.cpp:
#include <myapp/app.hpp>
int main()
{
return myapp::run();
}tests/test_app.cpp:
#include <vix.hpp>
#include <myapp/app.hpp>
int main()
{
if (myapp::add(10, 20) != 30)
{
vix::print("add test failed");
return 1;
}
vix::print("app tests passed");
return 0;
}Run:
cd tests
vix runMultiple test files
Tests can be split across several files.
Example:
mathlib/
tests/
vix.app
test_main.cpp
test_add.cpp
test_mul.cpptests/vix.app:
name = mathlib_tests
type = executable
standard = c++20
sources = [
test_main.cpp,
test_add.cpp,
test_mul.cpp,
../src/add.cpp,
../src/mul.cpp,
]
include_dirs = [
../include,
]test_main.cpp:
#include <vix.hpp>
int run_add_tests();
int run_mul_tests();
int main()
{
if (run_add_tests() != 0)
return 1;
if (run_mul_tests() != 0)
return 1;
vix::print("all tests passed");
return 0;
}test_add.cpp:
#include <vix.hpp>
#include <mathlib/math.hpp>
int run_add_tests()
{
if (mathlib::add(2, 3) != 5)
{
vix::print("add test failed");
return 1;
}
return 0;
}test_mul.cpp:
#include <vix.hpp>
#include <mathlib/math.hpp>
int run_mul_tests()
{
if (mathlib::mul(4, 5) != 20)
{
vix::print("mul test failed");
return 1;
}
return 0;
}Test resources
Tests sometimes need input files.
Example layout:
myapp/
tests/
vix.app
test_config.cpp
data/
config.jsontests/vix.app:
name = config_tests
type = executable
standard = c++20
output_dir = bin
sources = [
test_config.cpp,
]
resources = [
data,
]Build and run:
cd tests
vix runThe data directory is copied next to the test executable.
Custom test resource destination
You can also rename or nest resources:
resources = [
"data/config.json=config/config.json",
]This copies:
data/config.jsonto:
config/config.jsonnext to the test executable.
Tests with packages
A test target can use packages and links like any other target.
Example:
name = threaded_tests
type = executable
standard = c++20
sources = [
test_threads.cpp,
]
packages = [
Threads:REQUIRED,
]
links = [
Threads::Threads,
]The rule is the same:
packages finds packages
links links targetsTests with compile options
Test targets can have their own compiler options:
name = mathlib_tests
type = executable
standard = c++20
sources = [
test_math.cpp,
../src/add.cpp,
]
include_dirs = [
../include,
]
compile_options = [
-Wall,
-Wextra,
-Wpedantic,
]This keeps test build settings close to the test target.
Tests with defines
Test-specific definitions can be added with defines:
name = mathlib_tests
type = executable
standard = c++20
sources = [
test_math.cpp,
../src/add.cpp,
]
include_dirs = [
../include,
]
defines = [
MATHLIB_TESTING=1,
]Then in C++:
#ifdef MATHLIB_TESTING
// test-only behavior
#endifExamples
Examples follow the same rule as tests:
one example target = one vix.appRecommended layout:
mathlib/
examples/
basic/
vix.app
src/
main.cpp
advanced/
vix.app
src/
main.cppEach example can be built independently.
Basic example manifest
examples/basic/vix.app:
name = mathlib_basic_example
type = executable
standard = c++20
sources = [
src/main.cpp,
../../src/add.cpp,
../../src/mul.cpp,
]
include_dirs = [
../../include,
]examples/basic/src/main.cpp:
#include <vix.hpp>
#include <mathlib/math.hpp>
int main()
{
vix::print("2 + 3 =", mathlib::add(2, 3));
vix::print("4 * 5 =", mathlib::mul(4, 5));
return 0;
}Run:
cd examples/basic
vix runExample with resources
Example layout:
myapp/
examples/
resources/
vix.app
src/
main.cpp
assets/
message.txtexamples/resources/vix.app:
name = resources_example
type = executable
standard = c++20
output_dir = bin
sources = [
src/main.cpp,
]
resources = [
assets,
]Run:
cd examples/resources
vix runExample with packages
Example:
name = fmt_example
type = executable
standard = c++20
sources = [
src/main.cpp,
]
packages = [
fmt:REQUIRED,
]
links = [
fmt::fmt,
]This keeps package requirements explicit for the example itself.
Why examples should be independent
Independent examples are useful because they can be copied, tested, and understood alone.
A reader can enter the folder and run:
vix runwithout understanding the entire repository.
That makes examples better for documentation and debugging.
CI for tests
A simple CI flow for a library can run:
vix build
cd tests
vix build
vix runIf the project has examples:
cd examples/basic
vix build
vix runFor release builds:
vix build --preset releaseinside each target folder.
Why not CTest in vix.app V1
CTest is powerful, but it brings a larger model:
test registration
test discovery
test properties
fixtures
labels
parallel execution
working directoriesThat belongs in full CMake for now.
For vix.app V1, test executables are enough.
A test passes when the executable returns 0.
A test fails when it returns non-zero.
This keeps the model simple.
When to use CMake for tests
Use CMakeLists.txt when tests need:
CTest integration
GoogleTest discovery
FetchContent
many test targets
fixtures
generated test files
coverage tools
custom test commandsvix.app is best for simple test executables.
Common mistakes
Including main.cpp in tests
Wrong:
sources = [
test_app.cpp,
../src/main.cpp,
../src/app.cpp,
]This can create duplicate main() errors.
Correct:
sources = [
test_app.cpp,
../src/app.cpp,
]Forgetting relative paths
Inside tests/vix.app, this is usually wrong:
sources = [
src/add.cpp,
]Because the file is probably one level above:
sources = [
../src/add.cpp,
]Missing include_dirs
If your test includes:
#include <mathlib/math.hpp>you need:
include_dirs = [
../include,
]Trying to run a library target
If the root project is:
type = staticthen use:
vix buildTo run something, use:
tests/vix.appor:
examples/basic/vix.appPutting too much into one manifest
Avoid trying to describe an entire workspace in one vix.app.
Prefer:
one folder
one target
one manifestRecommended patterns
Library with tests
mathlib/
vix.app
include/
src/
tests/
vix.app
test_math.cppLibrary with examples
mathlib/
vix.app
include/
src/
examples/
basic/
vix.app
src/
main.cppApp with tests
myapp/
vix.app
include/
src/
tests/
vix.app
test_app.cppSummary
For tests and examples, keep the model simple:
root/vix.app -> main target
tests/vix.app -> test executable
examples/name/vix.app -> example executableUse explicit sources.
Use relative paths carefully.
Do not include main.cpp in test executables.
Use CMake when the test system becomes complex.
vix.app is strongest when every manifest describes one clear target.