vix.app Tests and Examples

vix.app is intentionally designed around one rule:

one vix.app = one target

That 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.app

This 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 resources

That 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 executable

Each 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.cpp

This gives three independent targets:

mathlib                 -> static library
mathlib_tests           -> test executable
mathlib_basic_example   -> example executable

Root 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 build

Test 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
../include

Because 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 run

Expected output:

all tests passed

Why 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.cpp

Root 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.cpp

but not:

../src/main.cpp

This 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.hpp

This 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 run

Multiple test files

Tests can be split across several files.

Example:

mathlib/
  tests/
    vix.app
    test_main.cpp
    test_add.cpp
    test_mul.cpp

tests/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.json

tests/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 run

The 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.json

to:

config/config.json

next 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 targets

Tests 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
#endif

Examples

Examples follow the same rule as tests:

one example target = one vix.app

Recommended layout:

mathlib/
  examples/
    basic/
      vix.app
      src/
        main.cpp
    advanced/
      vix.app
      src/
        main.cpp

Each 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 run

Example with resources

Example layout:

myapp/
  examples/
    resources/
      vix.app
      src/
        main.cpp
      assets/
        message.txt

examples/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 run

Example 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 run

without 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 run

If the project has examples:

cd examples/basic
vix build
vix run

For release builds:

vix build --preset release

inside 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 directories

That 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 commands

vix.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 = static

then use:

vix build

To run something, use:

tests/vix.app

or:

examples/basic/vix.app

Putting too much into one manifest

Avoid trying to describe an entire workspace in one vix.app.

Prefer:

one folder
one target
one manifest

Recommended patterns

Library with tests

mathlib/
  vix.app
  include/
  src/
  tests/
    vix.app
    test_math.cpp

Library with examples

mathlib/
  vix.app
  include/
  src/
  examples/
    basic/
      vix.app
      src/
        main.cpp

App with tests

myapp/
  vix.app
  include/
  src/
  tests/
    vix.app
    test_app.cpp

Summary

For tests and examples, keep the model simple:

root/vix.app              -> main target
tests/vix.app             -> test executable
examples/name/vix.app     -> example executable

Use 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.