539 lines
12 KiB
C
539 lines
12 KiB
C
/**
|
|
* @file test_measured_max.c
|
|
* @brief Explicit measured-max and working-load regression tests.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "trainlog/database.h"
|
|
#include "trainlog/measured_max.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 bool insert_reps_session(
|
|
TrainlogDatabase *database,
|
|
const char *session_id,
|
|
const char *started_at,
|
|
TrainlogSessionType session_type,
|
|
const char *exercise_id,
|
|
TrainlogLoadMode load_mode,
|
|
const int *reps,
|
|
const double *weights,
|
|
size_t set_count
|
|
)
|
|
{
|
|
TrainlogSetInput sets[4];
|
|
TrainlogSessionExerciseInput exercise;
|
|
TrainlogSessionInput session;
|
|
size_t index;
|
|
|
|
if (
|
|
database == NULL ||
|
|
session_id == NULL ||
|
|
started_at == NULL ||
|
|
exercise_id == NULL ||
|
|
reps == NULL ||
|
|
set_count == 0U ||
|
|
set_count > 4U
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
(void)memset(
|
|
sets,
|
|
0,
|
|
sizeof(sets)
|
|
);
|
|
|
|
for (
|
|
index = 0U;
|
|
index < set_count;
|
|
++index
|
|
) {
|
|
sets[index].reps =
|
|
reps[index];
|
|
|
|
if (
|
|
load_mode !=
|
|
TRAINLOG_LOAD_NONE
|
|
) {
|
|
if (weights == NULL) {
|
|
return false;
|
|
}
|
|
|
|
sets[index].has_weight = true;
|
|
sets[index].weight_kg =
|
|
weights[index];
|
|
}
|
|
}
|
|
|
|
(void)memset(
|
|
&exercise,
|
|
0,
|
|
sizeof(exercise)
|
|
);
|
|
|
|
(void)snprintf(
|
|
exercise.exercise_id,
|
|
sizeof(exercise.exercise_id),
|
|
"%s",
|
|
exercise_id
|
|
);
|
|
|
|
exercise.recording_mode =
|
|
TRAINLOG_RECORDING_SETS;
|
|
|
|
exercise.load_mode =
|
|
load_mode;
|
|
|
|
exercise.rest_seconds = 120;
|
|
exercise.target_sets =
|
|
(int)set_count;
|
|
exercise.target_reps = 1;
|
|
|
|
if (
|
|
load_mode !=
|
|
TRAINLOG_LOAD_NONE
|
|
) {
|
|
exercise.target_has_weight = true;
|
|
exercise.target_weight_kg =
|
|
weights[0];
|
|
}
|
|
|
|
exercise.sets = sets;
|
|
exercise.set_count = set_count;
|
|
|
|
(void)memset(
|
|
&session,
|
|
0,
|
|
sizeof(session)
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.session_id,
|
|
sizeof(session.session_id),
|
|
"%s",
|
|
session_id
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.started_at,
|
|
sizeof(session.started_at),
|
|
"%s",
|
|
started_at
|
|
);
|
|
|
|
(void)snprintf(
|
|
session.ended_at,
|
|
sizeof(session.ended_at),
|
|
"%s",
|
|
"2026-09-06T21:00:00+02:00"
|
|
);
|
|
|
|
session.session_type =
|
|
session_type;
|
|
|
|
session.exercises =
|
|
&exercise;
|
|
|
|
session.exercise_count = 1U;
|
|
|
|
return
|
|
trainlog_database_insert_session(
|
|
database,
|
|
&session
|
|
) == TRAINLOG_STATUS_OK;
|
|
}
|
|
|
|
static bool test_measured_max_semantics(void)
|
|
{
|
|
TrainlogDatabase *database = NULL;
|
|
|
|
TrainlogExercisePerformancePoint
|
|
points[16];
|
|
|
|
TrainlogMeasuredMaxSummary summary;
|
|
|
|
size_t count = 0U;
|
|
double working = 0.0;
|
|
|
|
const int one_rep[] = {1};
|
|
const int failed_then_one[] = {0, 1};
|
|
const int reps_20[] = {20};
|
|
const int reps_15[] = {15};
|
|
|
|
const double training_140[] = {140.0};
|
|
const double external_110[] = {110.0};
|
|
const double external_latest[] = {
|
|
115.0,
|
|
105.0,
|
|
};
|
|
const double assistance_30[] = {30.0};
|
|
const double assistance_35[] = {35.0};
|
|
|
|
CHECK(
|
|
trainlog_database_open(
|
|
":memory:",
|
|
&database
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
add_exercise(
|
|
database,
|
|
"ex_measured_external",
|
|
"measured external"
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
add_exercise(
|
|
database,
|
|
"ex_measured_assistance",
|
|
"measured assistance"
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
add_exercise(
|
|
database,
|
|
"ex_measured_none",
|
|
"measured none"
|
|
)
|
|
);
|
|
|
|
/*
|
|
* A stronger ordinary training set must never become a measured maximum.
|
|
*/
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_training_140",
|
|
"2026-09-01T18:00:00+02:00",
|
|
TRAINLOG_SESSION_TRAINING,
|
|
"ex_measured_external",
|
|
TRAINLOG_LOAD_EXTERNAL,
|
|
one_rep,
|
|
training_140,
|
|
1U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_external_old",
|
|
"2026-09-02T18:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_external",
|
|
TRAINLOG_LOAD_EXTERNAL,
|
|
one_rep,
|
|
external_110,
|
|
1U
|
|
)
|
|
);
|
|
|
|
/*
|
|
* 115 kg x 0 is a failed attempt. The newest successful measured result is
|
|
* 105 kg while the older historical measured record remains 110 kg.
|
|
*/
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_external_new",
|
|
"2026-09-03T18:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_external",
|
|
TRAINLOG_LOAD_EXTERNAL,
|
|
failed_then_one,
|
|
external_latest,
|
|
2U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_list_exercise_performance(
|
|
database,
|
|
"ex_measured_external",
|
|
points,
|
|
16U,
|
|
&count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(count == 3U);
|
|
|
|
CHECK(
|
|
points[0].session_type ==
|
|
TRAINLOG_SESSION_MAX_TEST
|
|
);
|
|
|
|
CHECK(
|
|
points[2].session_type ==
|
|
TRAINLOG_SESSION_TRAINING
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_measured_max_summarize(
|
|
points,
|
|
count,
|
|
&summary
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(summary.test_count == 2U);
|
|
CHECK(summary.successful_test_count == 2U);
|
|
CHECK(summary.found);
|
|
CHECK(summary.has_record);
|
|
|
|
CHECK(
|
|
strcmp(
|
|
summary.current.session_id,
|
|
"se_max_external_new"
|
|
) == 0
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.weight_kg >
|
|
104.99
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.weight_kg <
|
|
105.01
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.metric_value ==
|
|
1
|
|
);
|
|
|
|
CHECK(
|
|
summary.record.weight_kg >
|
|
109.99
|
|
);
|
|
|
|
CHECK(
|
|
summary.record.weight_kg <
|
|
110.01
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_measured_max_working_load(
|
|
&summary.current,
|
|
80.0,
|
|
2.5,
|
|
&working
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
working > 84.99 &&
|
|
working < 85.01
|
|
);
|
|
|
|
{
|
|
TrainlogLatestExplicitMax latest = {0};
|
|
latest.found = true;
|
|
latest.load_mode = TRAINLOG_LOAD_EXTERNAL;
|
|
latest.max_weight_kg = 105.0;
|
|
(void)snprintf(latest.equipment_id, sizeof(latest.equipment_id),
|
|
"%s", "barbell");
|
|
CHECK(trainlog_measured_max_target_load(&latest, "barbell",
|
|
TRAINLOG_LOAD_EXTERNAL, 73,
|
|
&working) == TRAINLOG_STATUS_OK);
|
|
CHECK(working > 76.649 && working < 76.651);
|
|
CHECK(trainlog_measured_max_target_load(&latest, "other",
|
|
TRAINLOG_LOAD_EXTERNAL, 73,
|
|
&working) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_measured_max_target_load(&latest, "barbell",
|
|
TRAINLOG_LOAD_ASSISTANCE, 73,
|
|
&working) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_measured_max_target_load(&latest, "barbell",
|
|
TRAINLOG_LOAD_EXTERNAL, 0,
|
|
&working) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
CHECK(trainlog_measured_max_target_load(&latest, "barbell",
|
|
TRAINLOG_LOAD_EXTERNAL, 101,
|
|
&working) == TRAINLOG_STATUS_INVALID_ARGUMENT);
|
|
}
|
|
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_assistance_old",
|
|
"2026-09-02T19:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_assistance",
|
|
TRAINLOG_LOAD_ASSISTANCE,
|
|
one_rep,
|
|
assistance_30,
|
|
1U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_assistance_new",
|
|
"2026-09-03T19:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_assistance",
|
|
TRAINLOG_LOAD_ASSISTANCE,
|
|
one_rep,
|
|
assistance_35,
|
|
1U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_list_exercise_performance(
|
|
database,
|
|
"ex_measured_assistance",
|
|
points,
|
|
16U,
|
|
&count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_measured_max_summarize(
|
|
points,
|
|
count,
|
|
&summary
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.weight_kg >
|
|
34.99
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.weight_kg <
|
|
35.01
|
|
);
|
|
|
|
CHECK(
|
|
summary.record.weight_kg >
|
|
29.99
|
|
);
|
|
|
|
CHECK(
|
|
summary.record.weight_kg <
|
|
30.01
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_measured_max_working_load(
|
|
&summary.current,
|
|
80.0,
|
|
2.5,
|
|
&working
|
|
) == TRAINLOG_STATUS_INVALID_ARGUMENT
|
|
);
|
|
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_none_old",
|
|
"2026-09-02T20:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_none",
|
|
TRAINLOG_LOAD_NONE,
|
|
reps_20,
|
|
NULL,
|
|
1U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
insert_reps_session(
|
|
database,
|
|
"se_max_none_new",
|
|
"2026-09-03T20:00:00+02:00",
|
|
TRAINLOG_SESSION_MAX_TEST,
|
|
"ex_measured_none",
|
|
TRAINLOG_LOAD_NONE,
|
|
reps_15,
|
|
NULL,
|
|
1U
|
|
)
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_database_list_exercise_performance(
|
|
database,
|
|
"ex_measured_none",
|
|
points,
|
|
16U,
|
|
&count
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
trainlog_measured_max_summarize(
|
|
points,
|
|
count,
|
|
&summary
|
|
) == TRAINLOG_STATUS_OK
|
|
);
|
|
|
|
CHECK(
|
|
summary.current.metric_value ==
|
|
15
|
|
);
|
|
|
|
CHECK(
|
|
summary.record.metric_value ==
|
|
20
|
|
);
|
|
|
|
trainlog_database_close(
|
|
database
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
CHECK(
|
|
test_measured_max_semantics()
|
|
);
|
|
|
|
(void)printf(
|
|
"PASS measured_max\n"
|
|
);
|
|
|
|
return 0;
|
|
}
|