All checks were successful
CI Code / Check spelling (push) Successful in 18s
CI Code / Check coding style (push) Successful in 29s
CI Code / Code Coverage (push) Successful in 2m44s
CI Code / Linux (debian) (push) Successful in 4m46s
CI Code / Linux (ubuntu) (push) Successful in 4m59s
CI Code / Linux (arch) (push) Successful in 5m56s
Add an AI client module that integrates with OpenAI-compatible API providers (OpenAI, Perplexity, and custom endpoints) to provide AI-assisted chat within CProof. Users can start sessions with /ai start, send prompts, receive responses in a dedicated AI window, switch between providers and models, and manage API keys — all with tab-completion. Providers are configured via /ai set commands with per-provider API keys, endpoints, default models, and custom settings. Two default providers (openai, perplexity) are seeded on first use. Provider state persists in [ai/<name>] sections of the preferences keyfile with automatic migration from the previous flat-key format. The /ai command integrates into the existing command system with 8 subcommands covering provider management, session lifecycle, model fetching, and conversation clearing. Autocomplete uses the standard flat prefix-matching chain for reliable tab-completion at every nesting level. A new ProfAiWin window type is added to the window system. Architecture: Async design: HTTP requests run on a background thread (pthread) to avoid blocking the ncurses UI loop; results are displayed on the main thread via direct function calls Thread safety: AIProvider and AISession use atomic ref-counting and mutex-protected session state; the request thread snapshots all session data before making the HTTP call Window validation: wins_ai_exists() prevents use-after-free when the user closes the AI window during an in-flight HTTP request (~60s) Privacy: store:false is sent with every request to prevent providers from persisting conversations or using them for training Response size limit: 10MB cap with immediate curl abort via CURL_WRITEFUNC_ERROR to prevent OOM JSON parsing uses unified helpers for both chat responses and error envelopes with consistent escape decoding. The response parser tries Perplexity /v1/responses "text" field first, then falls back to OpenAI "content". Error parsing extracts provider error.message from the standard envelope format. Model parsing handles multiple API response formats (OpenAI list, Perplexity, array) including edge cases. Tests include 470+ lines of unit tests covering provider management, session lifecycle, JSON parsing (multiple formats), autocomplete cycling, and error handling, plus functional tests for /ai command dispatch. A stub_ai.c module isolates unit tests from UI dependencies.
704 lines
28 KiB
Makefile
704 lines
28 KiB
Makefile
core_sources = \
|
||
src/xmpp/contact.c src/xmpp/contact.h \
|
||
src/log.c src/common.c \
|
||
src/chatlog.c src/chatlog.h \
|
||
src/database.h src/database.c \
|
||
src/database_flatfile.c src/database_flatfile.h \
|
||
src/database_flatfile_parser.c \
|
||
src/database_flatfile_verify.c \
|
||
src/database_export.c \
|
||
src/log.h src/profanity.c src/common.h \
|
||
src/profanity.h src/xmpp/chat_session.c \
|
||
src/xmpp/chat_session.h src/xmpp/muc.c src/xmpp/muc.h src/xmpp/jid.h src/xmpp/jid.c \
|
||
src/xmpp/chat_state.h src/xmpp/chat_state.c \
|
||
src/xmpp/resource.c src/xmpp/resource.h \
|
||
src/xmpp/roster_list.c src/xmpp/roster_list.h \
|
||
src/xmpp/xmpp.h src/xmpp/capabilities.c src/xmpp/session.c \
|
||
src/xmpp/connection.h src/xmpp/connection.c \
|
||
src/xmpp/iq.c src/xmpp/message.c src/xmpp/presence.c src/xmpp/stanza.c \
|
||
src/xmpp/stanza.h src/xmpp/message.h src/xmpp/iq.h src/xmpp/presence.h \
|
||
src/xmpp/capabilities.h src/xmpp/session.h \
|
||
src/xmpp/roster.c src/xmpp/roster.h \
|
||
src/xmpp/bookmark.c src/xmpp/bookmark.h \
|
||
src/xmpp/blocking.c src/xmpp/blocking.h \
|
||
src/xmpp/form.c src/xmpp/form.h \
|
||
src/xmpp/avatar.c src/xmpp/avatar.h \
|
||
src/xmpp/ox.c src/xmpp/ox.h \
|
||
src/xmpp/vcard.c src/xmpp/vcard.h src/xmpp/vcard_funcs.h \
|
||
src/event/common.c src/event/common.h \
|
||
src/event/server_events.c src/event/server_events.h \
|
||
src/event/client_events.c src/event/client_events.h \
|
||
src/ui/ui.h src/ui/window.c src/ui/window.h src/ui/core.c \
|
||
src/ui/titlebar.c src/ui/statusbar.c src/ui/inputwin.c \
|
||
src/ui/titlebar.h src/ui/statusbar.h src/ui/inputwin.h \
|
||
src/ui/screen.h src/ui/screen.c \
|
||
src/ui/console.c src/ui/notifier.c \
|
||
src/ui/win_types.h \
|
||
src/ui/window_list.c src/ui/window_list.h \
|
||
src/ui/rosterwin.c src/ui/occupantswin.c \
|
||
src/ui/buffer.c src/ui/buffer.h \
|
||
src/ui/aiwin.c \
|
||
src/ui/chatwin.c \
|
||
src/ui/mucwin.c \
|
||
src/ui/privwin.c \
|
||
src/ui/confwin.c \
|
||
src/ui/xmlwin.c \
|
||
src/ui/vcardwin.c \
|
||
src/command/cmd_defs.h src/command/cmd_defs.c \
|
||
src/command/cmd_funcs.h src/command/cmd_funcs.c \
|
||
src/command/cmd_ac.h src/command/cmd_ac.c \
|
||
src/tools/parser.c \
|
||
src/tools/parser.h \
|
||
src/tools/http_common.c \
|
||
src/tools/http_common.h \
|
||
src/tools/http_upload.c \
|
||
src/tools/http_upload.h \
|
||
src/tools/http_download.c \
|
||
src/tools/http_download.h \
|
||
src/tools/plugin_download.c \
|
||
src/tools/plugin_download.h \
|
||
src/tools/bookmark_ignore.c \
|
||
src/tools/bookmark_ignore.h \
|
||
src/tools/autocomplete.c src/tools/autocomplete.h \
|
||
src/tools/clipboard.c src/tools/clipboard.h \
|
||
src/tools/editor.c src/tools/editor.h \
|
||
src/config/files.c src/config/files.h \
|
||
src/config/conflists.c src/config/conflists.h \
|
||
src/config/accounts.c src/config/accounts.h \
|
||
src/config/tlscerts.c src/config/tlscerts.h \
|
||
src/config/account.c src/config/account.h \
|
||
src/config/preferences.c src/config/preferences.h \
|
||
src/config/theme.c src/config/theme.h \
|
||
src/config/color.c src/config/color.h \
|
||
src/config/scripts.c src/config/scripts.h \
|
||
src/config/cafile.c src/config/cafile.h \
|
||
src/plugins/plugins.h src/plugins/plugins.c \
|
||
src/plugins/api.h src/plugins/api.c \
|
||
src/plugins/callbacks.h src/plugins/callbacks.c \
|
||
src/plugins/autocompleters.c src/plugins/autocompleters.h \
|
||
src/plugins/themes.c src/plugins/themes.h \
|
||
src/plugins/settings.c src/plugins/settings.h \
|
||
src/plugins/disco.c src/plugins/disco.h \
|
||
src/ui/tray.h src/ui/tray.c \
|
||
src/ai/ai_client.h src/ai/ai_client.c
|
||
|
||
unittest_sources = \
|
||
src/xmpp/contact.c src/xmpp/contact.h src/common.c \
|
||
src/log.h src/profanity.c src/common.h \
|
||
src/profanity.h src/xmpp/chat_session.c \
|
||
src/xmpp/chat_session.h src/xmpp/muc.c src/xmpp/muc.h src/xmpp/jid.h src/xmpp/jid.c \
|
||
src/xmpp/resource.c src/xmpp/resource.h \
|
||
src/xmpp/chat_state.h src/xmpp/chat_state.c \
|
||
src/xmpp/roster_list.c src/xmpp/roster_list.h \
|
||
src/xmpp/xmpp.h src/xmpp/form.c \
|
||
src/ai/ai_client.h src/ai/ai_client.c \
|
||
src/ui/ui.h \
|
||
src/otr/otr.h \
|
||
src/pgp/gpg.h \
|
||
src/pgp/ox.h \
|
||
src/omemo/omemo.h \
|
||
src/omemo/crypto.h \
|
||
src/omemo/store.h \
|
||
src/xmpp/vcard.h src/xmpp/vcard_funcs.h \
|
||
src/database_flatfile.h \
|
||
src/database_flatfile_parser.c \
|
||
src/command/cmd_defs.h src/command/cmd_defs.c \
|
||
src/command/cmd_funcs.h src/command/cmd_funcs.c \
|
||
src/command/cmd_ac.h src/command/cmd_ac.c \
|
||
src/tools/parser.c \
|
||
src/tools/parser.h \
|
||
src/tools/autocomplete.c src/tools/autocomplete.h \
|
||
src/tools/clipboard.c src/tools/clipboard.h \
|
||
src/tools/editor.c src/tools/editor.h \
|
||
src/tools/bookmark_ignore.c \
|
||
src/tools/bookmark_ignore.h \
|
||
src/config/accounts.h \
|
||
src/config/account.c src/config/account.h \
|
||
src/config/files.c src/config/files.h \
|
||
src/config/tlscerts.c src/config/tlscerts.h \
|
||
src/config/preferences.c src/config/preferences.h \
|
||
src/config/theme.c src/config/theme.h \
|
||
src/config/color.c src/config/color.h \
|
||
src/config/scripts.c src/config/scripts.h \
|
||
src/config/conflists.c src/config/conflists.h \
|
||
src/plugins/plugins.h src/plugins/plugins.c \
|
||
src/plugins/api.h src/plugins/api.c \
|
||
src/plugins/callbacks.h src/plugins/callbacks.c \
|
||
src/plugins/autocompleters.c src/plugins/autocompleters.h \
|
||
src/plugins/themes.c src/plugins/themes.h \
|
||
src/plugins/settings.c src/plugins/settings.h \
|
||
src/plugins/disco.c src/plugins/disco.h \
|
||
src/ui/window_list.c src/ui/window_list.h \
|
||
src/event/common.c src/event/common.h \
|
||
src/event/server_events.c src/event/server_events.h \
|
||
src/event/client_events.c src/event/client_events.h \
|
||
src/ui/tray.h src/ui/tray.c \
|
||
tests/prof_cmocka.h \
|
||
tests/unittests/xmpp/stub_vcard.c \
|
||
tests/unittests/xmpp/stub_avatar.c \
|
||
tests/unittests/xmpp/stub_ox.c \
|
||
tests/unittests/xmpp/stub_xmpp.c \
|
||
tests/unittests/xmpp/stub_message.c \
|
||
tests/unittests/ui/stub_ui.c tests/unittests/ui/stub_ui.h \
|
||
tests/unittests/ui/stub_vcardwin.c \
|
||
tests/unittests/ui/stub_ai.c \
|
||
tests/unittests/log/stub_log.c \
|
||
tests/unittests/chatlog/stub_chatlog.c \
|
||
tests/unittests/database/stub_database.c \
|
||
tests/unittests/config/stub_accounts.c \
|
||
tests/unittests/config/stub_cafile.c \
|
||
tests/unittests/tools/stub_http_upload.c \
|
||
tests/unittests/tools/stub_http_download.c \
|
||
tests/unittests/tools/stub_aesgcm_download.c \
|
||
tests/unittests/tools/stub_plugin_download.c \
|
||
tests/unittests/helpers.c tests/unittests/helpers.h \
|
||
tests/unittests/test_form.c tests/unittests/test_form.h \
|
||
tests/unittests/test_common.c tests/unittests/test_common.h \
|
||
tests/unittests/test_autocomplete.c tests/unittests/test_autocomplete.h \
|
||
tests/unittests/test_jid.c tests/unittests/test_jid.h \
|
||
tests/unittests/test_parser.c tests/unittests/test_parser.h \
|
||
tests/unittests/test_roster_list.c tests/unittests/test_roster_list.h \
|
||
tests/unittests/test_chat_session.c tests/unittests/test_chat_session.h \
|
||
tests/unittests/test_contact.c tests/unittests/test_contact.h \
|
||
tests/unittests/test_preferences.c tests/unittests/test_preferences.h \
|
||
tests/unittests/test_server_events.c tests/unittests/test_server_events.h \
|
||
tests/unittests/test_muc.c tests/unittests/test_muc.h \
|
||
tests/unittests/test_cmd_presence.c tests/unittests/test_cmd_presence.h \
|
||
tests/unittests/test_cmd_alias.c tests/unittests/test_cmd_alias.h \
|
||
tests/unittests/test_cmd_connect.c tests/unittests/test_cmd_connect.h \
|
||
tests/unittests/test_cmd_rooms.c tests/unittests/test_cmd_rooms.h \
|
||
tests/unittests/test_cmd_account.c tests/unittests/test_cmd_account.h \
|
||
tests/unittests/test_cmd_sub.c tests/unittests/test_cmd_sub.h \
|
||
tests/unittests/test_cmd_bookmark.c tests/unittests/test_cmd_bookmark.h \
|
||
tests/unittests/test_cmd_otr.c tests/unittests/test_cmd_otr.h \
|
||
tests/unittests/test_cmd_pgp.c tests/unittests/test_cmd_pgp.h \
|
||
tests/unittests/test_cmd_join.c tests/unittests/test_cmd_join.h \
|
||
tests/unittests/test_cmd_roster.c tests/unittests/test_cmd_roster.h \
|
||
tests/unittests/test_cmd_disconnect.c tests/unittests/test_cmd_disconnect.h \
|
||
tests/unittests/test_callbacks.c tests/unittests/test_callbacks.h \
|
||
tests/unittests/test_plugins_disco.c tests/unittests/test_plugins_disco.h \
|
||
tests/unittests/test_forced_encryption.c tests/unittests/test_forced_encryption.h \
|
||
tests/unittests/test_ai_client.c tests/unittests/test_ai_client.h \
|
||
tests/unittests/test_database_export.c tests/unittests/test_database_export.h \
|
||
tests/unittests/test_database_stress.c tests/unittests/test_database_stress.h \
|
||
tests/unittests/unittests.c
|
||
|
||
functionaltest_sources = \
|
||
tests/prof_cmocka.h \
|
||
tests/functionaltests/proftest.c tests/functionaltests/proftest.h \
|
||
tests/functionaltests/test_connect.c tests/functionaltests/test_connect.h \
|
||
tests/functionaltests/test_ping.c tests/functionaltests/test_ping.h \
|
||
tests/functionaltests/test_rooms.c tests/functionaltests/test_rooms.h \
|
||
tests/functionaltests/test_presence.c tests/functionaltests/test_presence.h \
|
||
tests/functionaltests/test_message.c tests/functionaltests/test_message.h \
|
||
tests/functionaltests/test_chat_session.c tests/functionaltests/test_chat_session.h \
|
||
tests/functionaltests/test_carbons.c tests/functionaltests/test_carbons.h \
|
||
tests/functionaltests/test_receipts.c tests/functionaltests/test_receipts.h \
|
||
tests/functionaltests/test_roster.c tests/functionaltests/test_roster.h \
|
||
tests/functionaltests/test_software.c tests/functionaltests/test_software.h \
|
||
tests/functionaltests/test_muc.c tests/functionaltests/test_muc.h \
|
||
tests/functionaltests/test_disconnect.c tests/functionaltests/test_disconnect.h \
|
||
tests/functionaltests/test_history.c tests/functionaltests/test_history.h \
|
||
tests/functionaltests/test_lastactivity.c tests/functionaltests/test_lastactivity.h \
|
||
tests/functionaltests/test_autoping.c tests/functionaltests/test_autoping.h \
|
||
tests/functionaltests/test_disco.c tests/functionaltests/test_disco.h \
|
||
tests/functionaltests/test_export_import.c tests/functionaltests/test_export_import.h \
|
||
tests/functionaltests/test_ai.c tests/functionaltests/test_ai.h \
|
||
tests/functionaltests/functionaltests.c
|
||
|
||
main_source = src/main.c
|
||
|
||
python_sources = \
|
||
src/plugins/python_plugins.h src/plugins/python_plugins.c \
|
||
src/plugins/python_api.h src/plugins/python_api.c
|
||
|
||
c_sources = \
|
||
src/plugins/c_plugins.h src/plugins/c_plugins.c \
|
||
src/plugins/c_api.h src/plugins/c_api.c
|
||
|
||
git_include = src/gitversion.h
|
||
|
||
pgp_sources = \
|
||
src/pgp/gpg.h src/pgp/gpg.c \
|
||
src/pgp/ox.h src/pgp/ox.c
|
||
|
||
pgp_unittest_sources = \
|
||
tests/unittests/pgp/stub_gpg.c \
|
||
tests/unittests/pgp/stub_ox.c
|
||
|
||
otr4_sources = \
|
||
src/otr/otrlib.h src/otr/otrlibv4.c src/otr/otr.h src/otr/otr.c
|
||
|
||
omemo_sources = \
|
||
src/omemo/omemo.h src/omemo/omemo.c src/omemo/crypto.h src/omemo/crypto.c \
|
||
src/omemo/store.h src/omemo/store.c src/xmpp/omemo.h src/xmpp/omemo.c \
|
||
src/tools/aesgcm_download.h src/tools/aesgcm_download.c
|
||
|
||
omemo_unittest_sources = \
|
||
tests/unittests/omemo/stub_omemo.c
|
||
|
||
if BUILD_PYTHON_API
|
||
core_sources += $(python_sources)
|
||
unittest_sources += $(python_sources)
|
||
endif
|
||
|
||
if BUILD_C_API
|
||
core_sources += $(c_sources)
|
||
unittest_sources += $(c_sources)
|
||
endif
|
||
|
||
otr_unittest_sources = \
|
||
tests/unittests/otr/stub_otr.c
|
||
|
||
themes_sources = $(top_srcdir)/themes/*
|
||
|
||
icons_sources = $(top_srcdir)/icons/*
|
||
|
||
script_sources = bootstrap.sh
|
||
|
||
man1_sources = $(wildcard $(top_srcdir)/docs/profanity*.1)
|
||
|
||
if BUILD_PGP
|
||
core_sources += $(pgp_sources)
|
||
unittest_sources += $(pgp_unittest_sources)
|
||
endif
|
||
|
||
if BUILD_OTR
|
||
unittest_sources += $(otr_unittest_sources)
|
||
core_sources += $(otr4_sources)
|
||
endif
|
||
|
||
if BUILD_OMEMO
|
||
core_sources += $(omemo_sources)
|
||
unittest_sources += $(omemo_unittest_sources)
|
||
endif
|
||
|
||
sqlite_sources = src/database_sqlite.c
|
||
|
||
if BUILD_SQLITE
|
||
core_sources += $(sqlite_sources)
|
||
endif
|
||
|
||
all_c_sources = $(core_sources) $(unittest_sources) \
|
||
$(pgp_sources) $(pgp_unittest_sources) \
|
||
$(otr4_sources) $(otr_unittest_sources) \
|
||
$(omemo_sources) $(omemo_unittest_sources) \
|
||
$(c_sources) $(python_sources) \
|
||
$(main_source)
|
||
|
||
AM_CFLAGS = @AM_CFLAGS@ -I$(srcdir)/src
|
||
|
||
bin_PROGRAMS = profanity
|
||
profanity_SOURCES = $(core_sources) $(main_source)
|
||
if THEMES_INSTALL
|
||
profanity_themesdir = @THEMES_PATH@
|
||
profanity_themes_DATA = $(themes_sources)
|
||
endif
|
||
if INCLUDE_GIT_VERSION
|
||
BUILT_SOURCES = $(git_include)
|
||
endif
|
||
profanity_iconsdir = @ICONS_PATH@
|
||
profanity_icons_DATA = $(icons_sources)
|
||
|
||
if BUILD_C_API
|
||
lib_LTLIBRARIES = libprofanity.la
|
||
libprofanity_la_LDFLAGS = -version-info 0:0:0 -shared -no-undefined
|
||
libprofanity_la_SOURCES = src/plugins/profapi.c
|
||
|
||
library_includedir=$(includedir)
|
||
library_include_HEADERS = src/plugins/profapi.h
|
||
endif
|
||
|
||
TESTS = tests/unittests/unittests
|
||
check_PROGRAMS = tests/unittests/unittests
|
||
tests_unittests_unittests_CPPFLAGS = -I$(srcdir)/tests
|
||
tests_unittests_unittests_SOURCES = $(unittest_sources)
|
||
tests_unittests_unittests_LDADD = -lcmocka
|
||
|
||
# Functional tests require libstabber.
|
||
# They are only built when libstabber is available.
|
||
# See: https://github.com/profanity-im/profanity/pull/1010
|
||
# https://github.com/profanity-im/stabber/issues/5
|
||
#
|
||
# Note: We use forkpty() instead of libexpect for PTY handling.
|
||
|
||
if HAVE_STABBER
|
||
if HAVE_FORKPTY
|
||
TESTS += tests/functionaltests/functionaltests
|
||
check_PROGRAMS += tests/functionaltests/functionaltests
|
||
tests_functionaltests_functionaltests_SOURCES = $(functionaltest_sources)
|
||
tests_functionaltests_functionaltests_CPPFLAGS = -I$(srcdir)/tests
|
||
tests_functionaltests_functionaltests_CFLAGS = $(AM_CFLAGS)
|
||
tests_functionaltests_functionaltests_LDADD = -lcmocka -lstabber @FORKPTY_LIB@
|
||
|
||
# Parallel functional tests target (~3x faster than sequential)
|
||
# Usage: make check-functional-parallel
|
||
# To add more groups: increase FUNC_TEST_GROUPS and add group in functionaltests.c
|
||
FUNC_TEST_GROUPS = 1 2 3 4
|
||
|
||
check-functional-parallel: tests/functionaltests/functionaltests
|
||
@echo "Running functional tests in parallel ($(words $(FUNC_TEST_GROUPS)) groups)..."
|
||
@mkdir -p $(builddir)/test-logs $(builddir)/test-files
|
||
@pids=""; \
|
||
for g in $(FUNC_TEST_GROUPS); do \
|
||
./tests/functionaltests/functionaltests $$g > $(builddir)/test-logs/group$$g.log 2>&1 & \
|
||
pids="$$pids $$!"; \
|
||
done; \
|
||
failed=0; i=1; \
|
||
for pid in $$pids; do \
|
||
wait $$pid || { echo "Group $$i FAILED"; cat $(builddir)/test-logs/group$$i.log; failed=1; }; \
|
||
i=$$((i + 1)); \
|
||
done; \
|
||
echo "=== Test Results Summary ==="; \
|
||
grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*.log || true; \
|
||
if [ $$failed -ne 0 ]; then echo "FUNCTIONAL TESTS FAILED"; exit 1; fi; \
|
||
echo "All functional test groups passed!"
|
||
|
||
# Run functional tests with the flat-file database backend
|
||
# Usage: make check-functional-flatfile
|
||
check-functional-flatfile: tests/functionaltests/functionaltests
|
||
@echo "Running functional tests with flat-file backend ($(words $(FUNC_TEST_GROUPS)) groups)..."
|
||
@mkdir -p $(builddir)/test-logs $(builddir)/test-files
|
||
@pids=""; \
|
||
for g in $(FUNC_TEST_GROUPS); do \
|
||
PROF_FLATFILE=1 ./tests/functionaltests/functionaltests $$g > $(builddir)/test-logs/group$$g-flatfile.log 2>&1 & \
|
||
pids="$$pids $$!"; \
|
||
done; \
|
||
failed=0; i=1; \
|
||
for pid in $$pids; do \
|
||
wait $$pid || { echo "Group $$i FAILED (flatfile)"; cat $(builddir)/test-logs/group$$i-flatfile.log; failed=1; }; \
|
||
i=$$((i + 1)); \
|
||
done; \
|
||
echo "=== Flat-file Test Results Summary ==="; \
|
||
grep -E 'PASSED|FAILED|Running' $(builddir)/test-logs/group*-flatfile.log || true; \
|
||
if [ $$failed -ne 0 ]; then echo "FLAT-FILE FUNCTIONAL TESTS FAILED"; exit 1; fi; \
|
||
echo "All flat-file functional test groups passed!"
|
||
endif
|
||
endif
|
||
|
||
man1_MANS = $(man1_sources)
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Bench harness — synthetic database load tests. Not part of `make check`.
|
||
# Build only when invoked via `make bench` / `make bench-quick`.
|
||
# Sources live in tests/bench/. See tests/bench/README.md.
|
||
|
||
bench_common_sources = \
|
||
tests/bench/bench_stubs.c \
|
||
tests/bench/bench_common.c tests/bench/bench_common.h \
|
||
tests/bench/bench_csv.c tests/bench/bench_csv.h \
|
||
src/database_flatfile.c src/database_flatfile.h \
|
||
src/database_flatfile_parser.c \
|
||
src/database_flatfile_verify.c \
|
||
src/common.c src/common.h
|
||
|
||
# Sources needed only by the export/import bench (S7/S8). Pulls in the SQLite
|
||
# backend, the cross-backend export/import code, and the dispatcher.
|
||
bench_export_sources = \
|
||
src/database.c src/database.h \
|
||
src/database_sqlite.c \
|
||
src/database_export.c
|
||
|
||
EXTRA_PROGRAMS = \
|
||
tests/bench/gen_history \
|
||
tests/bench/bench_runner \
|
||
tests/bench/bench_long_messages \
|
||
tests/bench/bench_failure_modes \
|
||
tests/bench/bench_export_import
|
||
|
||
tests_bench_gen_history_SOURCES = tests/bench/gen_history.c $(bench_common_sources)
|
||
tests_bench_gen_history_CPPFLAGS = -I$(srcdir)/src -I$(srcdir)/tests/bench
|
||
tests_bench_gen_history_CFLAGS = $(AM_CFLAGS)
|
||
tests_bench_gen_history_LDADD =
|
||
|
||
tests_bench_bench_runner_SOURCES = tests/bench/bench_runner.c $(bench_common_sources)
|
||
tests_bench_bench_runner_CPPFLAGS = -I$(srcdir)/src -I$(srcdir)/tests/bench
|
||
tests_bench_bench_runner_CFLAGS = $(AM_CFLAGS)
|
||
tests_bench_bench_runner_LDADD =
|
||
|
||
tests_bench_bench_long_messages_SOURCES = tests/bench/bench_long_messages.c $(bench_common_sources)
|
||
tests_bench_bench_long_messages_CPPFLAGS = -I$(srcdir)/src -I$(srcdir)/tests/bench
|
||
tests_bench_bench_long_messages_CFLAGS = $(AM_CFLAGS)
|
||
tests_bench_bench_long_messages_LDADD =
|
||
|
||
tests_bench_bench_failure_modes_SOURCES = tests/bench/bench_failure_modes.c $(bench_common_sources)
|
||
tests_bench_bench_failure_modes_CPPFLAGS = -I$(srcdir)/src -I$(srcdir)/tests/bench
|
||
tests_bench_bench_failure_modes_CFLAGS = $(AM_CFLAGS)
|
||
tests_bench_bench_failure_modes_LDADD =
|
||
|
||
tests_bench_bench_export_import_SOURCES = tests/bench/bench_export_import.c \
|
||
$(bench_common_sources) $(bench_export_sources)
|
||
tests_bench_bench_export_import_CPPFLAGS = -I$(srcdir)/src -I$(srcdir)/tests/bench
|
||
tests_bench_bench_export_import_CFLAGS = $(AM_CFLAGS)
|
||
tests_bench_bench_export_import_LDADD =
|
||
|
||
# Volume control: BENCH_VOLUME={small|medium|max}. Default `small` is safe
|
||
# even on tight disks (~5 MB). `max` is heavyweight — see README.
|
||
BENCH_VOLUME ?= small
|
||
BENCH_DATA_DIR ?= /tmp/cproof-bench-corpus
|
||
BENCH_CSV ?= tests/bench/current.csv
|
||
|
||
bench-build: tests/bench/gen_history tests/bench/bench_runner tests/bench/bench_long_messages tests/bench/bench_failure_modes tests/bench/bench_export_import
|
||
|
||
# Resolve --lines / --years for the configured volume.
|
||
bench-gen: bench-build
|
||
@mkdir -p $(BENCH_DATA_DIR)
|
||
@case "$(BENCH_VOLUME)" in \
|
||
small) L=10000; Y=1; P=mixed ;; \
|
||
medium) L=500000; Y=5; P=mixed ;; \
|
||
max) L=5000000; Y=10; P=long ;; \
|
||
*) echo "BENCH_VOLUME must be small|medium|max"; exit 2 ;; \
|
||
esac; \
|
||
echo "==> generating volume=$(BENCH_VOLUME) lines=$$L years=$$Y profile=$$P"; \
|
||
./tests/bench/gen_history --lines=$$L --years=$$Y --msg-len-profile=$$P \
|
||
--lmc-rate=3 --mam-ooo-rate=0 --resources-per-contact=3 \
|
||
--output=$(BENCH_DATA_DIR) --seed=42
|
||
|
||
bench-run: bench-build
|
||
@echo "==> running scenarios (csv=$(BENCH_CSV))"
|
||
@BENCH_VOLUME=$(BENCH_VOLUME) BENCH_DATA_DIR=$(BENCH_DATA_DIR) \
|
||
./tests/bench/bench_runner --data=$(BENCH_DATA_DIR) --csv=$(BENCH_CSV)
|
||
@echo "==> CSV: $(BENCH_CSV)"
|
||
@cat $(BENCH_CSV)
|
||
|
||
bench: bench-gen bench-run
|
||
|
||
bench-quick:
|
||
@$(MAKE) bench BENCH_VOLUME=small
|
||
|
||
# Long-message tests (L1–L14). Independent of corpus volume, uses its own tmp dir.
|
||
bench-longmsg: bench-build
|
||
@echo "==> long-message tests (csv=$(BENCH_CSV))"
|
||
@./tests/bench/bench_long_messages \
|
||
--tmp=$(BENCH_DATA_DIR)-longmsg --csv=$(BENCH_CSV)
|
||
@rm -rf $(BENCH_DATA_DIR)-longmsg
|
||
|
||
# Failure-injection (F1–F15). Independent corpus, asserts behaviour on bad data.
|
||
bench-failure: bench-build
|
||
@echo "==> failure-injection tests (csv=$(BENCH_CSV))"
|
||
@./tests/bench/bench_failure_modes \
|
||
--tmp=$(BENCH_DATA_DIR)-fail --csv=$(BENCH_CSV)
|
||
@rm -rf $(BENCH_DATA_DIR)-fail
|
||
|
||
# Export/import (S7/S8) pipeline. Independent corpus under BENCH_DATA_DIR-export.
|
||
# Default scale: 100k rows (~5–30 s). Override with BENCH_PIPE_ROWS=N.
|
||
BENCH_PIPE_ROWS ?= 100000
|
||
|
||
BENCH_PIPE_VOLUME ?= pipe$(BENCH_PIPE_ROWS)
|
||
|
||
bench-export: bench-build
|
||
@echo "==> S7 export pipeline ($(BENCH_PIPE_ROWS) rows, csv=$(BENCH_CSV))"
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import seed --rows=$(BENCH_PIPE_ROWS) --account=bench@bench.example
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import export --account=bench@bench.example \
|
||
--csv=$(BENCH_CSV) --label=S7a_export_cold --volume=$(BENCH_PIPE_VOLUME)
|
||
@echo " -- second pass (dedup hot path)"
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import export --account=bench@bench.example \
|
||
--csv=$(BENCH_CSV) --label=S7b_export_dedup --volume=$(BENCH_PIPE_VOLUME)
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
|
||
bench-import: bench-build
|
||
@echo "==> S8 import pipeline ($(BENCH_PIPE_ROWS) rows, csv=$(BENCH_CSV))"
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
@# Seed account A, export it, then wipe its DB but keep flatlog. Import
|
||
@# back into the same account — that way the to: header in flatfile
|
||
@# matches the importing account so dedup works correctly.
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import seed --rows=$(BENCH_PIPE_ROWS) --account=bench@bench.example
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import export --account=bench@bench.example \
|
||
--csv=$(BENCH_CSV) --label=S7_seed_export --volume=$(BENCH_PIPE_VOLUME)
|
||
@rm -f $(BENCH_DATA_DIR)-export/database/bench_at_bench.example/chatlog.db*
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import import --account=bench@bench.example \
|
||
--csv=$(BENCH_CSV) --label=S8a_import_cold --volume=$(BENCH_PIPE_VOLUME)
|
||
@echo " -- second import (idempotency)"
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import import --account=bench@bench.example \
|
||
--csv=$(BENCH_CSV) --label=S8b_import_idempotent --volume=$(BENCH_PIPE_VOLUME)
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
|
||
# Full roundtrip with content diff at default volume. PASSes only if
|
||
# every row in the rebuilt DB matches the source byte-for-byte.
|
||
bench-roundtrip: bench-build
|
||
@echo "==> S8e roundtrip+full diff ($(BENCH_PIPE_ROWS) rows, csv=$(BENCH_CSV))"
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
@BENCH_DATA_DIR=$(BENCH_DATA_DIR)-export \
|
||
./tests/bench/bench_export_import roundtrip --rows=$(BENCH_PIPE_ROWS) \
|
||
--csv=$(BENCH_CSV) --label=S8e_roundtrip --volume=$(BENCH_PIPE_VOLUME) --full-diff
|
||
@rm -rf $(BENCH_DATA_DIR)-export
|
||
|
||
# Pipeline at medium volume (default BENCH_PIPE_ROWS=100k).
|
||
bench-pipeline: bench-export bench-import bench-roundtrip
|
||
|
||
# Pipeline at MAX volume — 1M rows. Heavy: ~5–10 min, ~500MB SQLite + ~500MB
|
||
# flatfile + ~500MB second SQLite. Run only when you can afford the time
|
||
# and the disk. Set BENCH_PIPE_ROWS_MAX to override (default 1_000_000).
|
||
BENCH_PIPE_ROWS_MAX ?= 1000000
|
||
bench-pipeline-max: bench-build
|
||
@echo "==> bench-pipeline-max: $(BENCH_PIPE_ROWS_MAX) rows (heavy!)"
|
||
@$(MAKE) bench-export BENCH_PIPE_ROWS=$(BENCH_PIPE_ROWS_MAX)
|
||
@$(MAKE) bench-import BENCH_PIPE_ROWS=$(BENCH_PIPE_ROWS_MAX)
|
||
@$(MAKE) bench-roundtrip BENCH_PIPE_ROWS=$(BENCH_PIPE_ROWS_MAX)
|
||
|
||
# S9: 200 contacts × 50k lines per contact = 10M lines distributed.
|
||
bench-multicontact: bench-build
|
||
@mkdir -p $(BENCH_DATA_DIR)-multi
|
||
@echo "==> S9: 200 contacts x 5000 lines"
|
||
@./tests/bench/gen_history --lines=1000000 --contacts=200 --years=3 \
|
||
--msg-len-profile=mixed --output=$(BENCH_DATA_DIR)-multi --seed=42
|
||
@BENCH_VOLUME=multicontact BENCH_DATA_DIR=$(BENCH_DATA_DIR)-multi \
|
||
./tests/bench/bench_runner --data=$(BENCH_DATA_DIR)-multi --csv=$(BENCH_CSV) \
|
||
--scenarios=S4,S6
|
||
@rm -rf $(BENCH_DATA_DIR)-multi
|
||
|
||
# S10: 30% LMC corrections.
|
||
bench-lmc: bench-build
|
||
@mkdir -p $(BENCH_DATA_DIR)-lmc
|
||
@echo "==> S10: LMC-heavy corpus (30%% corrections)"
|
||
@./tests/bench/gen_history --lines=100000 --years=2 --lmc-rate=30 \
|
||
--msg-len-profile=mixed --output=$(BENCH_DATA_DIR)-lmc --seed=42
|
||
@BENCH_VOLUME=lmc BENCH_DATA_DIR=$(BENCH_DATA_DIR)-lmc \
|
||
./tests/bench/bench_runner --data=$(BENCH_DATA_DIR)-lmc --csv=$(BENCH_CSV)
|
||
@rm -rf $(BENCH_DATA_DIR)-lmc
|
||
|
||
# S11: 20% MAM out-of-order timestamps.
|
||
bench-ooo: bench-build
|
||
@mkdir -p $(BENCH_DATA_DIR)-ooo
|
||
@echo "==> S11: MAM-OOO corpus (20%% out-of-order)"
|
||
@./tests/bench/gen_history --lines=100000 --years=2 --mam-ooo-rate=20 \
|
||
--msg-len-profile=mixed --output=$(BENCH_DATA_DIR)-ooo --seed=42
|
||
@BENCH_VOLUME=ooo BENCH_DATA_DIR=$(BENCH_DATA_DIR)-ooo \
|
||
./tests/bench/bench_runner --data=$(BENCH_DATA_DIR)-ooo --csv=$(BENCH_CSV) \
|
||
--scenarios=S6
|
||
@rm -rf $(BENCH_DATA_DIR)-ooo
|
||
|
||
# Run everything: P1 scenarios + long messages + failure injection + multicontact + LMC + OOO.
|
||
bench-full: bench bench-longmsg bench-failure bench-multicontact bench-lmc bench-ooo
|
||
|
||
# Compare current.csv against baseline.csv and exit non-zero on regressions.
|
||
BENCH_BASELINE ?= tests/bench/baseline.csv
|
||
BENCH_THRESHOLD ?= 25
|
||
bench-compare:
|
||
@python3 tests/bench/compare_baseline.py \
|
||
--baseline=$(BENCH_BASELINE) --current=$(BENCH_CSV) \
|
||
--threshold=$(BENCH_THRESHOLD)
|
||
|
||
# Snapshot current.csv as the new baseline. Run this only after manually
|
||
# reviewing that the numbers look healthy.
|
||
bench-update-baseline:
|
||
@cp $(BENCH_CSV) $(BENCH_BASELINE)
|
||
@echo "baseline updated: $(BENCH_BASELINE)"
|
||
|
||
bench-clean:
|
||
rm -rf $(BENCH_DATA_DIR) $(BENCH_DATA_DIR)-longmsg $(BENCH_DATA_DIR)-fail \
|
||
$(BENCH_DATA_DIR)-multi $(BENCH_DATA_DIR)-lmc $(BENCH_DATA_DIR)-ooo \
|
||
$(BENCH_DATA_DIR)-export $(BENCH_CSV)
|
||
|
||
.PHONY: bench bench-quick bench-build bench-gen bench-run bench-clean \
|
||
bench-longmsg bench-failure bench-multicontact bench-lmc bench-ooo \
|
||
bench-full bench-compare bench-update-baseline \
|
||
bench-export bench-import bench-roundtrip bench-pipeline bench-pipeline-max
|
||
|
||
EXTRA_DIST = $(man1_sources) $(icons_sources) $(themes_sources) $(script_sources) profrc.example theme_template LICENSE.txt README.md CHANGELOG
|
||
|
||
# Ship API documentation with `make dist`
|
||
EXTRA_DIST += \
|
||
apidocs/c/c-prof.conf \
|
||
apidocs/c/gen.sh \
|
||
apidocs/c/profapi.h \
|
||
apidocs/c/profhooks.h \
|
||
apidocs/python/conf.py \
|
||
apidocs/python/gen.sh \
|
||
apidocs/python/index.rst \
|
||
apidocs/python/Makefile \
|
||
apidocs/python/src/plugin.py \
|
||
apidocs/python/src/prof.py
|
||
|
||
# Bench harness sources (not built by default; see EXTRA_PROGRAMS above)
|
||
EXTRA_DIST += \
|
||
tests/bench/README.md \
|
||
tests/bench/compare_baseline.py \
|
||
tests/bench/gen_history.c \
|
||
tests/bench/bench_runner.c \
|
||
tests/bench/bench_long_messages.c \
|
||
tests/bench/bench_failure_modes.c \
|
||
tests/bench/bench_export_import.c \
|
||
tests/bench/bench_stubs.c \
|
||
tests/bench/bench_common.c tests/bench/bench_common.h \
|
||
tests/bench/bench_csv.c tests/bench/bench_csv.h \
|
||
tests/bench/baseline.csv
|
||
|
||
if INCLUDE_GIT_VERSION
|
||
EXTRA_DIST += .git/HEAD .git/index
|
||
|
||
$(git_include).in: .git/HEAD .git/index
|
||
rm -f $@
|
||
echo "#ifndef PROF_GIT_BRANCH" >> $@
|
||
echo "#define PROF_GIT_BRANCH \"$(shell git rev-parse --symbolic-full-name --abbrev-ref HEAD)\"" >> $@
|
||
echo "#endif" >> $@
|
||
echo "#ifndef PROF_GIT_REVISION" >> $@
|
||
echo "#define PROF_GIT_REVISION \"$(shell git log --pretty=format:'%h' -n 1)\"" >> $@
|
||
echo "#endif" >> $@
|
||
|
||
#
|
||
# Create $(git_include) atomically to catch possible race. The race can occur
|
||
# when $(git_include) is generated in parallel with building of src/profanity.c.
|
||
# So this hack allows to find and fix the problem earlier.
|
||
#
|
||
$(git_include): $(git_include).in
|
||
cp $< $@
|
||
|
||
clean-local:
|
||
rm -f $(git_include) $(git_include).in
|
||
endif
|
||
|
||
clean-functional-tests:
|
||
rm -rf $(builddir)/test-files $(builddir)/test-logs
|
||
|
||
.PHONY: my-prof.supp clean-functional-tests
|
||
my-prof.supp:
|
||
@sed '/^# AUTO-GENERATED START/q' prof.supp > $@
|
||
@printf "\n\n# glib\n" >> $@
|
||
@cat /usr/share/glib-2.0/valgrind/glib.supp >> $@
|
||
@printf "\n\n# Python\n" >> $@
|
||
@wget -O- https://raw.githubusercontent.com/python/cpython/refs/tags/v`python3 --version | cut -d' ' -f2`/Misc/valgrind-python.supp >> $@
|
||
@printf "\n\n# gtk\n" >> $@
|
||
@test -z "@GTK_VERSION@" || wget -O- https://raw.githubusercontent.com/GNOME/gtk/refs/tags/@GTK_VERSION@/gtk.supp >> $@
|
||
@printf "\n\n# more gtk\n" >> $@
|
||
@test -z "@GTK_VERSION@" || cat /usr/share/gtk-3.0/valgrind/gtk.supp >> $@
|
||
|
||
check-unit: tests/unittests/unittests
|
||
tests/unittests/unittests
|
||
|
||
@VALGRIND_CHECK_RULES@
|
||
VALGRIND_SUPPRESSIONS_FILES=$(srcdir)/prof.supp
|
||
|
||
# Code coverage targets (requires --enable-coverage)
|
||
coverage-clean:
|
||
find . -name '*.gcda' -delete
|
||
find . -name '*.gcno' -delete
|
||
rm -rf coverage-html coverage.info
|
||
|
||
coverage-report: check
|
||
lcov --capture --directory . --output-file coverage.info --ignore-errors inconsistent
|
||
lcov --remove coverage.info '/usr/*' '*/tests/*' --output-file coverage.info --ignore-errors inconsistent
|
||
genhtml coverage.info --output-directory coverage-html
|
||
@echo "Coverage report generated in coverage-html/index.html"
|
||
|
||
.PHONY: coverage-clean coverage-report
|
||
|
||
format: $(all_c_sources)
|
||
clang-format -i $(all_c_sources)
|
||
|
||
format-sources: $(core_sources) $(main_source)
|
||
clang-format -i $^
|
||
|
||
spell:
|
||
codespell
|
||
|
||
doublecheck: format check spell
|