Skip to content
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
108 changes: 107 additions & 1 deletion scripts/of.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash
# of.sh - openFrameworks CLI | Dan Rosser 2025
OF_SCRIPT_VERSION=0.4.2
OF_SCRIPT_VERSION=0.4.3

OF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
OF_DIR="$(realpath "$OF_DIR/../")"
Expand Down Expand Up @@ -955,6 +955,7 @@ printHelp(){
cleanup projects|caches|libs Free disk (artifacts / downloads / prebuilts)
version of | pg Version info
upgrade addons | apps Upgrade tree
test [group] Run tests/ smoke tests (build + run), or menu for bash smoke scripts too
installed Alias for status
apothecary Build libraries via apothecary submodule

Expand Down Expand Up @@ -1037,6 +1038,56 @@ menuVersion(){
printf '\n'
}

# test() — runs either the tests/*/* smoke test projects (build + run,
# reusing cmdTest from of_build.sh) or one of the standalone smoke_test_*.sh
# bash scripts under scripts/dev/ (e.g. smoke_test_nightly.sh).
menuTest(){
local choice
if ! menuCanRun; then
echoWarning "no TTY — running all tests/ smoke tests non-interactively"
cmdTest
return $?
fi
printBanner "test"

local -a opts=("All tests/ smoke tests (build + run)|tests-all")
local g gName
if [[ -d "${OF_DIR}/tests" ]]; then
for g in "${OF_DIR}/tests"/*/; do
[[ -d "$g" ]] || continue
gName=$(basename "${g%/}")
opts+=("tests/${gName} only|tests-${gName}")
done
fi
local -a smokeScripts=()
local s
for s in "${OF_CORE_SCRIPT_DIR}/dev"/smoke_test_*.sh; do
[[ -f "$s" ]] && smokeScripts+=("$(basename "$s")")
done
for s in "${smokeScripts[@]}"; do
opts+=("Smoke script — ${s}|script:${s}")
done
opts+=("Back|back")

menuPick "Run which tests?" "${opts[@]}" || return 2
choice="$UI_MENU_RESULT"
case "$choice" in
tests-all) cmdTest ;;
tests-*) cmdTest "${choice#tests-}" ;;
script:*)
local scriptName="${choice#script:}"
local scriptPath="${OF_CORE_SCRIPT_DIR}/dev/${scriptName}"
local -a args=()
if menuCanRun && confirmYes "Include the real network/build checks (--real)?"; then
args+=(--real)
fi
ensureScript "$scriptPath" 2>/dev/null
bash "$scriptPath" "${args[@]}"
;;
back) return 2 ;;
esac
}

OF_LIB_STATE_FILE="${OF_DIR}/libs/.of-cli-state"

# Essentials used to decide "installed"
Expand Down Expand Up @@ -1341,6 +1392,50 @@ menuPickLibSource(){
export LIB_SOURCE
}

menuPickLibPlatform(){
local source="${1:-$LIB_SOURCE}"
local current="${2:-$OF_PLATFORM}"
local -a opts=()
case "$source" in
apothecary)
opts=(
"osx — desktop host libraries|osx"
"macos — multi-target XCFrameworks (iOS, macOS, tvOS)|macos"
"ios — iPhone / iPad|ios"
"android|android"
"linux|linux"
"emscripten|emscripten"
"msys2|msys2"
"vs — Visual Studio|vs"
)
;;
oflibs)
opts=(
"osx / macos|osx"
"linux|linux"
"linux aarch64|linuxaarch64"
"emscripten|emscripten"
"Visual Studio|vs"
)
;;
archive)
opts=(
"osx|osx"
"ios|ios"
"android|android"
"linux|linux"
"msys2|msys2"
"Visual Studio|vs"
)
;;
esac
[[ ${#opts[@]} -gt 0 ]] || return 1
echoNote "detected platform: ${current}${OF_ARCH:+ / ${OF_ARCH}}"
menuPick "Libraries for which platform?" "${opts[@]}" || return 1
LIB_PLATFORM="$UI_MENU_RESULT"
export LIB_PLATFORM
}

menuPickLibTag(){
local source="${1:-$LIB_SOURCE}"
local -a opts=()
Expand Down Expand Up @@ -1411,6 +1506,9 @@ menuDownloadLibs(){

menuPickLibSource || return 2
echoSuccess "source → $LIB_SOURCE"
menuPickLibPlatform "$LIB_SOURCE" "$platformDir" || return 2
platformDir="$LIB_PLATFORM"
echoSuccess "platform → $platformDir"
menuPickLibTag "$LIB_SOURCE" || return 2
echoSuccess "tag → $LIB_TAG"

Expand Down Expand Up @@ -2326,6 +2424,7 @@ cmdMenu(){
"draw() — build (menuBuild)|build" \
"cleanup() — free space (menuCleanup)|cleanup" \
"version() — info (menuVersion)|version" \
"test() — smoke tests (menuTest)|test" \
"exit()|exit"
then
echoInfo "bye"
Expand All @@ -2340,6 +2439,7 @@ cmdMenu(){
build) menuBuild ;;
cleanup|clean) menuCleanup ;;
version) menuVersion; menuPause ;;
test) menuTest; [[ $? -eq 2 ]] || menuPause ;;
exit|quit) echoSuccess "bye"; return 0 ;;
*) echoError "unknown: $choice"; menuPause ;;
esac
Expand Down Expand Up @@ -2481,6 +2581,12 @@ runCommand(){
esac
;;
upgrade) cmdUpgrade "$subcmd" ;;
test)
case "${subcmd:-}" in
""|menu) menuTest ;;
*) cmdTest "$subcmd" ;;
esac
;;
cleanup|clean)
case "${subcmd:-}" in
""|menu) menuCleanup ;;
Expand Down
125 changes: 125 additions & 0 deletions scripts/of_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,131 @@ runNativeApp(){
return 1
}

# Finds a project's Debug binary and runs it synchronously, returning its
# exit code. Unlike runNativeApp (which `open`s GUI apps asynchronously and
# can't report pass/fail), this is for headless test binaries built with
# ofAppNoWindow — used by `of test`.
findTestBinary(){
local project="$1" name
name=$(basename "$project")
local -a candidates=(
"${project}/bin/${name}_debug.app/Contents/MacOS/${name}_debug"
"${project}/bin/${name}_debug"
"${project}/bin/${name}_debug.exe"
)
local c
for c in "${candidates[@]}"; do
[[ -x "$c" ]] && { printf '%s' "$c"; return 0; }
done
return 1
}

# Builds (Debug) and runs one tests/<group>/<name> project, streaming its
# ofxUnitTests output. Returns the test binary's exit code.
runTestProject(){
local project="$1"
local buildLog
buildLog=$(mktemp)

ensureProjectMakefile "$project" || {
echoError "no Makefile and could not install template Makefile"
rm -f "$buildLog"
return 1
}
if ! ( cd "$project" && make -j"$(ofBuildJobs)" Debug ) >"$buildLog" 2>&1; then
echoError "build failed"
tail -25 "$buildLog" | sed 's/^/ /'
rm -f "$buildLog"
return 1
fi
rm -f "$buildLog"

local bin
bin=$(findTestBinary "$project") || {
echoError "built but no debug binary found under bin/"
return 1
}
"$bin"
}

# Discovers and runs every tests/<group>/<name> project (or just tests/<group>
# if $1 is given), mirroring what scripts/ci/*/run_tests.sh do in CI but
# reusable from the CLI/menu. Prints a tick per test, full output only on
# failure, and a pass/fail summary at the end.
cmdTest(){
local filterGroup="${1:-}"
local testsDir="${OF_DIR}/tests"
[[ -d "$testsDir" ]] || { echoError "no tests/ directory found"; return 1; }

printBanner "test"
echoInfo "running smoke tests under tests/${filterGroup:+$filterGroup/}"
printf '\n'

local -a groups=()
local g
if [[ -n "$filterGroup" ]]; then
[[ -d "${testsDir}/${filterGroup}" ]] || { echoError "no tests/${filterGroup} folder"; return 1; }
groups=("${testsDir}/${filterGroup}")
else
for g in "${testsDir}"/*/; do
[[ -d "$g" ]] && groups+=("${g%/}")
done
fi

local total=0 passed=0 failed=0
local -a failedNames=()
local group groupName test name t0 t1 runLog

for group in "${groups[@]}"; do
groupName=$(basename "$group")
printf ' %s%s%s\n' "$C_BOLD" "$groupName" "$C_RESET"
for test in "$group"/*/; do
[[ -d "$test" && -d "${test}src" ]] || continue
test="${test%/}"
name=$(basename "$test")
total=$((total + 1))
# \r-overwrite the pending marker only when actually attached to a
# terminal -- otherwise (piped/logged output) it just garbles two
# lines together, so print plain sequential lines instead
if isInteractive; then
printf ' %s·%s %s/%s' "$C_MUTED" "$C_RESET" "$groupName" "$name"
else
printf ' · %s/%s ... ' "$groupName" "$name"
fi
runLog=$(mktemp)
t0=$(date +%s)
if runTestProject "$test" >"$runLog" 2>&1; then
t1=$(date +%s)
if isInteractive; then
printf '\r %s✓%s %s/%s %s(%ss)%s\n' "$C_OK" "$C_RESET" "$groupName" "$name" "$C_MUTED" "$((t1 - t0))" "$C_RESET"
else
printf 'ok (%ss)\n' "$((t1 - t0))"
fi
passed=$((passed + 1))
else
t1=$(date +%s)
if isInteractive; then
printf '\r %s✗%s %s/%s %s(%ss)%s\n' "$C_ERR" "$C_RESET" "$groupName" "$name" "$C_MUTED" "$((t1 - t0))" "$C_RESET"
else
printf 'FAILED (%ss)\n' "$((t1 - t0))"
fi
failed=$((failed + 1))
failedNames+=("${groupName}/${name}")
sed 's/^/ /' "$runLog"
fi
rm -f "$runLog"
done
done

printf '\n'
if [[ "$failed" -eq 0 ]]; then
echoSuccess "${passed}/${total} tests passed"
return 0
fi
echoError "${failed}/${total} tests failed: ${failedNames[*]}"
return 1
}

# ---------------------------------------------------------------------------
# High-level command: build one project with a system
# ---------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions tests/libs/freetypeTest/addons.make
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ofxUnitTests
28 changes: 28 additions & 0 deletions tests/libs/freetypeTest/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include "ofMain.h"
#include "ofAppNoWindow.h"
#include "ofxUnitTests.h"
#include <ft2build.h>
#include FT_FREETYPE_H

class ofApp: public ofxUnitTestsApp{
void run(){
FT_Library library;
FT_Error err = FT_Init_FreeType(&library);
ofxTestEq(err, (FT_Error)0, "FT_Init_FreeType() succeeds");

FT_Int major = 0, minor = 0, patch = 0;
FT_Library_Version(library, &major, &minor, &patch);
ofxTest(major > 0 || minor > 0, "FT_Library_Version() reports a sane version");

FT_Error doneErr = FT_Done_FreeType(library);
ofxTestEq(doneErr, (FT_Error)0, "FT_Done_FreeType() succeeds");
}
};

int main(){
ofInit();
auto window = std::make_shared<ofAppNoWindow>();
auto app = std::make_shared<ofApp>();
ofRunApp(window, app);
return ofRunMainLoop();
}
1 change: 1 addition & 0 deletions tests/libs/glmTest/addons.make
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ofxUnitTests
34 changes: 34 additions & 0 deletions tests/libs/glmTest/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#include "ofMain.h"
#include "ofAppNoWindow.h"
#include "ofxUnitTests.h"
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

class ofApp: public ofxUnitTestsApp{
void run(){
glm::mat4 identity(1.0f);
glm::vec4 v(1.0f, 2.0f, 3.0f, 1.0f);
glm::vec4 transformed = identity * v;
ofxTestEq(transformed.x, v.x, "identity matrix leaves x unchanged");
ofxTestEq(transformed.y, v.y, "identity matrix leaves y unchanged");
ofxTestEq(transformed.z, v.z, "identity matrix leaves z unchanged");

glm::mat4 translated = glm::translate(identity, glm::vec3(10.0f, 0.0f, 0.0f));
glm::vec4 moved = translated * glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
ofxTestEq(moved.x, 10.0f, "translate() moves x by 10");

glm::vec3 a(1.0f, 0.0f, 0.0f);
glm::vec3 b(0.0f, 1.0f, 0.0f);
ofxTestEq(glm::dot(a, b), 0.0f, "perpendicular unit vectors dot to 0");
glm::vec3 crossed = glm::cross(a, b);
ofxTestEq(crossed.z, 1.0f, "cross(x,y) == z");
}
};

int main(){
ofInit();
auto window = std::make_shared<ofAppNoWindow>();
auto app = std::make_shared<ofApp>();
ofRunApp(window, app);
return ofRunMainLoop();
}
1 change: 1 addition & 0 deletions tests/libs/jsonTest/addons.make
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ofxUnitTests
31 changes: 31 additions & 0 deletions tests/libs/jsonTest/src/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "ofMain.h"
#include "ofAppNoWindow.h"
#include "ofxUnitTests.h"
#include "json.hpp"

class ofApp: public ofxUnitTestsApp{
void run(){
using nlohmann::json;

auto parsed = json::parse(R"({"name":"openFrameworks","version":12,"addons":["ofxGui","ofxOsc"]})");
ofxTestEq(parsed["name"].get<std::string>(), std::string("openFrameworks"), "parses string field");
ofxTestEq(parsed["version"].get<int>(), 12, "parses int field");
ofxTestEq(parsed["addons"].size(), (size_t)2, "parses array field");
ofxTestEq(parsed["addons"][0].get<std::string>(), std::string("ofxGui"), "array element 0");

json built;
built["ok"] = true;
built["count"] = 3;
ofxTestEq(built.dump(), std::string("{\"count\":3,\"ok\":true}"), "serializes back to json");

ofxTest(!json::accept(std::string("{not valid json")), "rejects malformed json");
}
};

int main(){
ofInit();
auto window = std::make_shared<ofAppNoWindow>();
auto app = std::make_shared<ofApp>();
ofRunApp(window, app);
return ofRunMainLoop();
}
1 change: 1 addition & 0 deletions tests/libs/opensslTest/addons.make
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ofxUnitTests
Loading
Loading