Skip to content

Commit c7d5ded

Browse files
committed
feat: init integration tests
1 parent 28d9aef commit c7d5ded

File tree

4 files changed

+300
-10
lines changed

4 files changed

+300
-10
lines changed

.github/workflows/main.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ jobs:
4747
run:
4848
shell: bash
4949

50+
env:
51+
CONNECTION_STRING: ${{ secrets.CONNECTION_STRING }}
52+
APIKEY: ${{ secrets.APIKEY }}
53+
WEBLITE: ${{ secrets.WEBLITE }}
54+
5055
steps:
5156

5257
- uses: actions/[email protected]
@@ -99,6 +104,9 @@ jobs:
99104
cat > commands.sh << EOF
100105
mv -f /data/local/tmp/sqlite3 /system/xbin
101106
cd /data/local/tmp
107+
export CONNECTION_STRING="$CONNECTION_STRING"
108+
export APIKEY="$APIKEY"
109+
export WEBLITE="$WEBLITE"
102110
$(make test PLATFORM=$PLATFORM ARCH=$ARCH -n)
103111
EOF
104112
echo "::endgroup::"

Makefile

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,14 @@ CURL_SRC = $(CURL_DIR)/src/curl-$(CURL_VERSION)
4242
COV_DIR = coverage
4343
CUSTOM_CSS = $(TEST_DIR)/sqliteai.css
4444

45-
# Files and objects
46-
ifeq ($(PLATFORM),windows)
47-
TEST_TARGET := $(DIST_DIR)/test.exe
48-
else
49-
TEST_TARGET := $(DIST_DIR)/test
50-
endif
5145
SRC_FILES = $(wildcard $(SRC_DIR)/*.c)
52-
TEST_FILES = $(SRC_FILES) $(wildcard $(TEST_DIR)/*.c) $(wildcard $(SQLITE_DIR)/*.c)
46+
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
47+
TEST_FILES = $(SRC_FILES) $(TEST_SRC) $(wildcard $(SQLITE_DIR)/*.c)
5348
RELEASE_OBJ = $(patsubst %.c, $(BUILD_RELEASE)/%.o, $(notdir $(SRC_FILES)))
5449
TEST_OBJ = $(patsubst %.c, $(BUILD_TEST)/%.o, $(notdir $(TEST_FILES)))
5550
COV_FILES = $(filter-out $(SRC_DIR)/lz4.c $(SRC_DIR)/network.c, $(SRC_FILES))
5651
CURL_LIB = $(CURL_DIR)/$(PLATFORM)/libcurl.a
52+
TEST_TARGET = $(patsubst %.c,$(DIST_DIR)/%$(EXE), $(notdir $(TEST_SRC)))
5753

5854
# Platform-specific settings
5955
ifeq ($(PLATFORM),windows)
@@ -64,6 +60,7 @@ ifeq ($(PLATFORM),windows)
6460
DEF_FILE := $(BUILD_RELEASE)/cloudsync.def
6561
CFLAGS += -DCURL_STATICLIB
6662
CURL_CONFIG = --with-schannel CFLAGS="-DCURL_STATICLIB"
63+
EXE = .exe
6764
else ifeq ($(PLATFORM),macos)
6865
TARGET := $(DIST_DIR)/cloudsync.dylib
6966
LDFLAGS += -arch x86_64 -arch arm64 -framework Security -dynamiclib -undefined dynamic_lookup
@@ -146,20 +143,21 @@ endif
146143

147144
# Test executable
148145
$(TEST_TARGET): $(TEST_OBJ)
149-
$(CC) $(TEST_OBJ) -o $@ $(T_LDFLAGS)
146+
$(CC) $(filter-out $(patsubst $(DIST_DIR)/%$(EXE),$(BUILD_TEST)/%.o, $(filter-out $@,$(TEST_TARGET))), $(TEST_OBJ)) -o $@ $(T_LDFLAGS)
150147

151148
# Object files
152149
$(BUILD_RELEASE)/%.o: %.c
153150
$(CC) $(CFLAGS) -O3 -fPIC -c $< -o $@
154151
$(BUILD_TEST)/sqlite3.o: $(SQLITE_DIR)/sqlite3.c
155-
$(CC) $(CFLAGS) -DSQLITE_CORE=1 -c $< -o $@
152+
$(CC) $(CFLAGS) -DSQLITE_CORE -c $< -o $@
156153
$(BUILD_TEST)/%.o: %.c
157154
$(CC) $(T_CFLAGS) -c $< -o $@
158155

159156
# Run code coverage (--css-file $(CUSTOM_CSS))
160157
test: $(TARGET) $(TEST_TARGET)
158+
sqlite3 health-track.sqlite < test/health-track-schema.sql
161159
$(SQLITE3) ":memory:" -cmd ".bail on" ".load ./$<" "SELECT cloudsync_version();"
162-
./$(TEST_TARGET)
160+
set -e; for t in $(TEST_TARGET); do ./$$t; done
163161
ifneq ($(COVERAGE),false)
164162
mkdir -p $(COV_DIR)
165163
lcov --capture --directory . --output-file $(COV_DIR)/coverage.info $(subst src, --include src,${COV_FILES})

test/health-track-schema.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- SQL schema for initializing the health tracking database
2+
CREATE TABLE IF NOT EXISTS users (
3+
id TEXT PRIMARY KEY NOT NULL,
4+
name TEXT UNIQUE NOT NULL DEFAULT ''
5+
);
6+
CREATE TABLE IF NOT EXISTS activities (
7+
id TEXT PRIMARY KEY NOT NULL,
8+
user_id TEXT,
9+
km REAL,
10+
bpm INTEGER,
11+
time TEXT,
12+
activity_type TEXT NOT NULL DEFAULT 'running',
13+
FOREIGN KEY(user_id) REFERENCES users(id)
14+
);
15+
CREATE TABLE IF NOT EXISTS workouts (
16+
id TEXT PRIMARY KEY NOT NULL,
17+
assigned_user_id TEXT,
18+
day_of_week TEXT,
19+
km REAL,
20+
max_time TEXT
21+
);

test/main.c

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
//
2+
// main.c
3+
// sqlite-sync
4+
//
5+
// Created by Gioele Cantoni on 05/06/25.
6+
// Set CONNECTION_STRING, APIKEY and WEBLITE environment variables before running this test.
7+
//
8+
9+
#include <stdio.h>
10+
#include <stdlib.h>
11+
#include "sqlite3.h"
12+
13+
#define DB_PATH "health-track.sqlite"
14+
#define EXT_PATH "./dist/cloudsync"
15+
#define ABORT_TEST abort_test: if (rc != SQLITE_OK) printf("Error: %s\n", sqlite3_errmsg(db)); if (db) sqlite3_close(db); return rc;
16+
17+
typedef enum { PRINT, NOPRINT, INT, GT0 } expected_type;
18+
19+
typedef struct {
20+
expected_type type;
21+
union {
22+
int i;
23+
const char *s; // for future use, if needed
24+
} value;
25+
} expected_t;
26+
27+
static int callback(void *data, int argc, char **argv, char **names) {
28+
expected_t *expect = (expected_t *)data;
29+
30+
switch(expect->type) {
31+
case NOPRINT: break;
32+
case PRINT:
33+
for (int i = 0; i < argc; i++) {
34+
printf("%s: %s ", names[i], argv[i] ? argv[i] : "NULL");
35+
}
36+
printf("\n");
37+
return SQLITE_OK;
38+
39+
case INT:
40+
if(argc == 1){
41+
int res = atoi(argv[0]);
42+
43+
if(res != expect->value.i){
44+
printf("Error: expected from %s: %d, got %d\n", names[0], expect->value.i, res);
45+
return SQLITE_ERROR;
46+
}
47+
48+
} else goto multiple_columns;
49+
break;
50+
51+
case GT0:
52+
if(argc == 1){
53+
int res = atoi(argv[0]);
54+
55+
if(!(res > 0)){
56+
printf("Error: expected from %s: to be greater than 0, got %d\n", names[0], res);
57+
return SQLITE_ERROR;
58+
}
59+
60+
} else goto multiple_columns;
61+
break;
62+
63+
default:
64+
printf("Error: unknown expect type\n");
65+
return SQLITE_ERROR;
66+
}
67+
68+
return SQLITE_OK;
69+
70+
multiple_columns:
71+
printf("Error: expected 1 column, got %d\n", argc);
72+
return SQLITE_ERROR;
73+
}
74+
75+
int db_exec (sqlite3 *db, const char *sql) {
76+
expected_t data;
77+
data.type = NOPRINT;
78+
79+
int rc = sqlite3_exec(db, sql, callback, &data, NULL);
80+
if (rc != SQLITE_OK) printf("Error while executing %s: %s\n", sql, sqlite3_errmsg(db));
81+
return rc;
82+
}
83+
84+
int db_print (sqlite3 *db, const char *sql) {
85+
expected_t data;
86+
data.type = PRINT;
87+
88+
int rc = sqlite3_exec(db, sql, callback, &data, NULL);
89+
if (rc != SQLITE_OK) printf("Error while executing %s: %s\n", sql, sqlite3_errmsg(db));
90+
return rc;
91+
}
92+
93+
int db_expect_int (sqlite3 *db, const char *sql, int expect) {
94+
expected_t data;
95+
data.type = INT;
96+
data.value.i = expect;
97+
98+
int rc = sqlite3_exec(db, sql, callback, &data, NULL);
99+
if (rc != SQLITE_OK) printf("Error while executing %s: %s\n", sql, sqlite3_errmsg(db));
100+
return rc;
101+
}
102+
103+
int db_expect_gt0 (sqlite3 *db, const char *sql) {
104+
expected_t data;
105+
data.type = GT0;
106+
107+
int rc = sqlite3_exec(db, sql, callback, &data, NULL);
108+
if (rc != SQLITE_OK) printf("Error while executing %s: %s\n", sql, sqlite3_errmsg(db));
109+
return rc;
110+
}
111+
112+
int open_load_ext(const char *db_path, sqlite3 **out_db) {
113+
sqlite3 *db = NULL;
114+
int rc = sqlite3_open(db_path, &db);
115+
if (rc != SQLITE_OK) goto abort_test;
116+
117+
// enable load extension
118+
rc = sqlite3_enable_load_extension(db, 1);
119+
if (rc != SQLITE_OK) goto abort_test;
120+
121+
rc = db_exec(db, "SELECT load_extension('"EXT_PATH"');");
122+
if (rc != SQLITE_OK) goto abort_test;
123+
124+
*out_db = db;
125+
return rc;
126+
127+
ABORT_TEST
128+
}
129+
130+
// MARK: -
131+
132+
int db_init (const char *db_path){
133+
sqlite3 *db = NULL;
134+
int rc = sqlite3_open(db_path, &db);
135+
if (rc != SQLITE_OK) goto abort_test;
136+
137+
rc = db_exec(db, "\
138+
CREATE TABLE IF NOT EXISTS users (\
139+
id TEXT PRIMARY KEY NOT NULL,\
140+
name TEXT UNIQUE NOT NULL DEFAULT ''\
141+
);\
142+
CREATE TABLE IF NOT EXISTS activities (\
143+
id TEXT PRIMARY KEY NOT NULL,\
144+
user_id TEXT,\
145+
km REAL,\
146+
bpm INTEGER,\
147+
time TEXT,\
148+
activity_type TEXT NOT NULL DEFAULT 'running',\
149+
FOREIGN KEY(user_id) REFERENCES users(id)\
150+
);\
151+
CREATE TABLE IF NOT EXISTS workouts (\
152+
id TEXT PRIMARY KEY NOT NULL,\
153+
assigned_user_id TEXT,\
154+
day_of_week TEXT,\
155+
km REAL,\
156+
max_time TEXT\
157+
);");
158+
if (rc != SQLITE_OK) goto abort_test;
159+
160+
ABORT_TEST
161+
}
162+
163+
int test_init (const char *db_path) {
164+
sqlite3 *db = NULL;
165+
int rc = open_load_ext(db_path, &db);
166+
167+
rc = db_exec(db, "SELECT cloudsync_init('users');"); if (rc != SQLITE_OK) goto abort_test;
168+
rc = db_exec(db, "SELECT cloudsync_init('activities');"); if (rc != SQLITE_OK) goto abort_test;
169+
rc = db_exec(db, "SELECT cloudsync_init('workouts');"); if (rc != SQLITE_OK) goto abort_test;
170+
171+
// init network with connection string + apikey
172+
char network_init[512];
173+
snprintf(network_init, sizeof(network_init), "SELECT cloudsync_network_init('%s?apikey=%s');", getenv("CONNECTION_STRING"), getenv("APIKEY"));
174+
rc = db_exec(db, network_init); if (rc != SQLITE_OK) goto abort_test;
175+
176+
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM users;", 0); if (rc != SQLITE_OK) goto abort_test;
177+
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM activities;", 0); if (rc != SQLITE_OK) goto abort_test;
178+
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM workouts;", 0); if (rc != SQLITE_OK) goto abort_test;
179+
rc = db_expect_gt0(db, "SELECT cloudsync_network_sync();"); if (rc != SQLITE_OK) goto abort_test;
180+
rc = db_expect_gt0(db, "SELECT COUNT(*) as count FROM users;"); if (rc != SQLITE_OK) goto abort_test;
181+
rc = db_expect_gt0(db, "SELECT COUNT(*) as count FROM activities;"); if (rc != SQLITE_OK) goto abort_test;
182+
rc = db_expect_int(db, "SELECT COUNT(*) as count FROM workouts;", 0); if (rc != SQLITE_OK) goto abort_test;
183+
rc = db_exec(db, "SELECT cloudsync_terminate();");
184+
185+
ABORT_TEST
186+
}
187+
188+
int test_is_enabled(const char *db_path) {
189+
sqlite3 *db = NULL;
190+
int rc = open_load_ext(db_path, &db);
191+
192+
rc = db_expect_int(db, "SELECT cloudsync_is_enabled('users');", 1); if (rc != SQLITE_OK) goto abort_test;
193+
rc = db_expect_int(db, "SELECT cloudsync_is_enabled('activities');", 1); if (rc != SQLITE_OK) goto abort_test;
194+
rc = db_expect_int(db, "SELECT cloudsync_is_enabled('workouts');", 1);
195+
196+
ABORT_TEST
197+
}
198+
199+
int test_db_version(const char *db_path) {
200+
sqlite3 *db = NULL;
201+
int rc = open_load_ext(db_path, &db);
202+
203+
rc = db_expect_gt0(db, "SELECT cloudsync_db_version();"); if (rc != SQLITE_OK) goto abort_test;
204+
rc = db_expect_gt0(db, "SELECT cloudsync_db_version_next();");
205+
206+
ABORT_TEST
207+
}
208+
209+
int test_enable_disable(const char *db_path) {
210+
sqlite3 *db = NULL;
211+
int rc = open_load_ext(db_path, &db);
212+
213+
rc = db_exec(db, "SELECT cloudsync_init('*');"); if (rc != SQLITE_OK) goto abort_test;
214+
rc = db_exec(db, "SELECT cloudsync_disable('users');"); if (rc != SQLITE_OK) goto abort_test;
215+
rc = db_exec(db, "INSERT INTO users (id, name) VALUES ('12afb', 'provaCmeaakbefa');"); if (rc != SQLITE_OK) goto abort_test;
216+
rc = db_exec(db, "SELECT cloudsync_enable('users');"); if (rc != SQLITE_OK) goto abort_test;
217+
218+
// init network with connection string + apikey
219+
char network_init[512];
220+
snprintf(network_init, sizeof(network_init), "SELECT cloudsync_network_init('%s?apikey=%s');", getenv("CONNECTION_STRING"), getenv("APIKEY"));
221+
rc = db_exec(db, network_init); if (rc != SQLITE_OK) goto abort_test;
222+
223+
rc = db_exec(db, "SELECT cloudsync_network_sync();"); if (rc != SQLITE_OK) goto abort_test;
224+
rc = db_exec(db, "SELECT cloudsync_cleanup('*');");
225+
226+
ABORT_TEST
227+
}
228+
229+
int version(){
230+
sqlite3 *db = NULL;
231+
int rc = open_load_ext(":memory:", &db);
232+
233+
rc = db_print(db, "SELECT cloudsync_version();");
234+
if (rc != SQLITE_OK) goto abort_test;
235+
236+
ABORT_TEST
237+
}
238+
239+
// MARK: -
240+
241+
int test_report(const char *description, int rc){
242+
printf("%-24s %s\n", description, rc ? "FAILED" : "OK");
243+
return rc;
244+
}
245+
246+
int main (void) {
247+
int rc = SQLITE_OK;
248+
249+
printf("\n\nIntegration Test ");
250+
rc += version();
251+
printf("===========================================\n");
252+
test_report("Version Test:", rc);
253+
254+
//rc += db_init(DB_PATH); // fix first the schema hash comparison
255+
rc += test_report("Init+Sync Test:", test_init(DB_PATH));
256+
rc += test_report("Is Enabled Test:", test_is_enabled(DB_PATH));
257+
rc += test_report("DB Version Test:", test_db_version(DB_PATH));
258+
rc += test_report("Enable Disable Test:", test_enable_disable(DB_PATH));
259+
260+
remove(DB_PATH); // clean up the database file
261+
printf("\n");
262+
return rc;
263+
}

0 commit comments

Comments
 (0)