trainlog/tui/tests/test_max_results.c

198 lines
8.4 KiB
C

/**
* @file test_max_results.c
* @brief Explicit measured-max persistence and bounded migration regressions.
*/
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sqlite3.h>
#include "trainlog/database.h"
#define CHECK(condition) \
do { \
if (!(condition)) { \
(void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", \
__FILE__, __LINE__, #condition); \
return false; \
} \
} while (0)
static bool add_exercise(TrainlogDatabase *database, const char *id,
const char *name)
{
return trainlog_database_insert_exercise(database, id, name, name,
TRAINLOG_TRACKING_REPS) == TRAINLOG_STATUS_OK;
}
static void max_input(TrainlogSessionExerciseInput *input,
const char *entry_id, const char *exercise_id,
double weight)
{
(void)memset(input, 0, sizeof(*input));
(void)snprintf(input->entry_id, sizeof(input->entry_id), "%s", entry_id);
(void)snprintf(input->exercise_id, sizeof(input->exercise_id), "%s",
exercise_id);
(void)snprintf(input->equipment_id, sizeof(input->equipment_id), "%s",
"rear_delt_pec_fly");
input->recording_mode = TRAINLOG_RECORDING_SETS;
input->load_mode = TRAINLOG_LOAD_NONE;
input->has_max_weight = true;
input->max_weight_kg = weight;
}
static bool test_explicit_max_round_trip_and_identity(void)
{
TrainlogDatabase *database = NULL;
TrainlogSessionExerciseInput entries[3];
TrainlogSessionInput session;
TrainlogSessionSummary summary;
TrainlogPersistedExerciseDetail details[3];
TrainlogEditableExerciseRecord editable[3];
TrainlogExercisePerformancePoint points[2];
size_t count = 0U;
size_t set_count = 0U;
CHECK(trainlog_database_open(":memory:", &database) == TRAINLOG_STATUS_OK);
CHECK(add_exercise(database, "ex_pec", "Pec Fly"));
CHECK(add_exercise(database, "ex_rear", "Rear Delt Fly"));
CHECK(trainlog_database_insert_exercise_profiled(database,
"ex_walk", "Marche", "marche", TRAINLOG_TRACKING_DURATION,
TRAINLOG_RECORDING_CONTINUOUS, TRAINLOG_EXERCISE_DATA_SPEED_KMH) ==
TRAINLOG_STATUS_OK);
max_input(&entries[0], "sxe_pec", "ex_pec", 100.0);
max_input(&entries[1], "sxe_rear", "ex_rear", 86.0);
(void)memset(&entries[2], 0, sizeof(entries[2]));
(void)snprintf(entries[2].entry_id, sizeof(entries[2].entry_id), "%s",
"sxe_walk");
(void)snprintf(entries[2].exercise_id, sizeof(entries[2].exercise_id), "%s",
"ex_walk");
entries[2].recording_mode = TRAINLOG_RECORDING_CONTINUOUS;
entries[2].data_fields = TRAINLOG_EXERCISE_DATA_SPEED_KMH;
entries[2].load_mode = TRAINLOG_LOAD_NONE;
entries[2].continuous_duration_seconds = 600;
entries[2].continuous_has_speed = true;
entries[2].continuous_speed_kmh = 5.5;
(void)memset(&session, 0, sizeof(session));
(void)snprintf(session.session_id, sizeof(session.session_id), "%s",
"se_explicit_max");
(void)snprintf(session.started_at, sizeof(session.started_at), "%s",
"2031-02-03T08:15:00+01:00");
session.session_type = TRAINLOG_SESSION_MAX_TEST;
session.exercises = entries;
session.exercise_count = 3U;
CHECK(trainlog_database_insert_session(database, &session) ==
TRAINLOG_STATUS_OK);
CHECK(trainlog_database_get_session_details(database, "se_explicit_max",
&summary, details, 3U, &count) == TRAINLOG_STATUS_OK);
CHECK(count == 3U);
CHECK(details[0].has_max_weight != 0 && details[0].max_weight_kg == 100.0);
CHECK(details[1].has_max_weight != 0 && details[1].max_weight_kg == 86.0);
CHECK(details[0].actual_set_count == 0U && details[1].actual_set_count == 0U);
CHECK(strcmp(details[0].entry_id, "sxe_pec") == 0);
CHECK(strcmp(details[0].equipment_id, details[1].equipment_id) == 0);
CHECK(details[2].continuous_duration_seconds == 600);
CHECK(details[2].has_continuous_speed != 0 &&
details[2].continuous_speed_kmh == 5.5);
CHECK(trainlog_database_load_session_editable(database, "se_explicit_max",
&summary, editable, 3U, &count, NULL, 0U, &set_count) ==
TRAINLOG_STATUS_OK);
CHECK(count == 3U && set_count == 0U);
CHECK(editable[2].continuous_duration_seconds == 600);
CHECK(editable[2].has_continuous_speed != 0 &&
editable[2].continuous_speed_kmh == 5.5);
CHECK(trainlog_database_list_exercise_performance(database, "ex_pec",
points, 2U, &count) == TRAINLOG_STATUS_OK);
CHECK(count == 1U && points[0].has_performance != 0);
CHECK(points[0].has_explicit_max != 0);
CHECK(points[0].actual_set_count == 0U && points[0].weight_kg == 100.0);
CHECK(strcmp(points[0].equipment_id, "rear_delt_pec_fly") == 0);
entries[0].max_weight_kg = 101.5;
CHECK(trainlog_database_replace_session_exercises(database,
"se_explicit_max", entries, 3U) == TRAINLOG_STATUS_OK);
CHECK(trainlog_database_get_session_details(database, "se_explicit_max",
&summary, details, 3U, &count) == TRAINLOG_STATUS_OK);
CHECK(details[0].max_weight_kg == 101.5);
CHECK(strcmp(details[0].entry_id, "sxe_pec") == 0);
session.session_type = TRAINLOG_SESSION_TRAINING;
(void)snprintf(session.session_id, sizeof(session.session_id), "%s",
"se_invalid_training_max");
CHECK(trainlog_database_insert_session(database, &session) ==
TRAINLOG_STATUS_INVALID_ARGUMENT);
trainlog_database_close(database);
return true;
}
static bool test_v8_migration_refuses_to_guess_multiple_attempts(void)
{
static const char *const SQL =
"CREATE TABLE sessions(id INTEGER PRIMARY KEY,session_type TEXT);"
"CREATE TABLE session_exercises(id INTEGER PRIMARY KEY,"
"session_row_id INTEGER,recording_mode TEXT);"
"CREATE TABLE performed_sets(id INTEGER PRIMARY KEY,"
"session_exercise_row_id INTEGER,position INTEGER,reps INTEGER,"
"duration_seconds INTEGER,weight_kg REAL);"
"INSERT INTO sessions VALUES(1,'max_test');"
"INSERT INTO session_exercises VALUES(10,1,'sets');"
"INSERT INTO session_exercises VALUES(11,1,'sets');"
"INSERT INTO performed_sets VALUES(20,10,0,1,NULL,100.0);"
"INSERT INTO performed_sets VALUES(21,11,0,1,NULL,80.0);"
"INSERT INTO performed_sets VALUES(22,11,1,1,NULL,86.0);"
"PRAGMA user_version=8;";
char path[] = "/tmp/trainlog-max-v8-XXXXXX";
sqlite3 *raw = NULL;
sqlite3_stmt *statement = NULL;
TrainlogDatabase *database = NULL;
int fd = mkstemp(path);
CHECK(fd >= 0 && close(fd) == 0);
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
CHECK(sqlite3_exec(raw, SQL, NULL, NULL, NULL) == SQLITE_OK);
CHECK(sqlite3_close(raw) == SQLITE_OK);
CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK);
trainlog_database_close(database);
CHECK(sqlite3_open_v2(path, &raw, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK);
CHECK(sqlite3_prepare_v2(raw,
"SELECT session_exercise_row_id,max_weight_kg FROM max_results;",
-1, &statement, NULL) == SQLITE_OK);
CHECK(sqlite3_step(statement) == SQLITE_ROW);
CHECK(sqlite3_column_int(statement, 0) == 10);
CHECK(sqlite3_column_double(statement, 1) == 100.0);
CHECK(sqlite3_step(statement) == SQLITE_DONE);
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
CHECK(sqlite3_prepare_v2(raw,
"SELECT position,reps,weight_kg FROM performed_sets "
"WHERE session_exercise_row_id=11 ORDER BY position;",
-1, &statement, NULL) == SQLITE_OK);
CHECK(sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_double(statement, 2) == 80.0);
CHECK(sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_double(statement, 2) == 86.0);
CHECK(sqlite3_step(statement) == SQLITE_DONE);
CHECK(sqlite3_finalize(statement) == SQLITE_OK);
CHECK(sqlite3_close(raw) == SQLITE_OK);
CHECK(unlink(path) == 0);
return true;
}
int main(void)
{
if (!test_explicit_max_round_trip_and_identity() ||
!test_v8_migration_refuses_to_guess_multiple_attempts()) {
return 1;
}
(void)printf("max results: PASS\n");
return 0;
}