mirror of
https://github.com/radareorg/r2xsql.git
synced 2026-11-07 06:36:02 -10:00
Fix release builds and SQL session cleanup
This commit is contained in:
1 parent
104599f12a
commit
9df012ee10
10 files changed
+151
-62
No files matched your search
@@ -2,7 +2,7 @@
|
||||
# Stage the same plugins/ layout on every platform for r2pm -bi.
|
||||
set -euo pipefail
|
||||
|
||||
# The pinned radare2 predates r2pm -H R2PM_TRIPLET.
|
||||
# Match the r2pm OS/architecture/bits naming used by the package recipe.
|
||||
case "${RUNNER_OS}-${RUNNER_ARCH}" in
|
||||
Linux-X64) R2PM_TRIPLET=linux-x86-64; ext=so ;;
|
||||
Windows-X64) R2PM_TRIPLET=windows-x86-64; ext=dll ;;
|
||||
@@ -11,17 +11,24 @@ case "${RUNNER_OS}-${RUNNER_ARCH}" in
|
||||
*) echo "Unsupported runner: ${RUNNER_OS}-${RUNNER_ARCH}" >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
export LD_LIBRARY_PATH="$PWD/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
export DYLD_LIBRARY_PATH="$PWD/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
|
||||
actual_r2=$(./r2prefix/bin/radare2 -NN -H R2_VERSION)
|
||||
[[ "$actual_r2" == "$R2_REF" ]] || {
|
||||
echo "Expected radare2 $R2_REF, found $actual_r2" >&2; exit 1;
|
||||
}
|
||||
|
||||
rm -rf dist
|
||||
mkdir -p dist/plugin/plugins
|
||||
mkdir -p dist/plugin/plugins dist/plugin/licenses
|
||||
cp "build/bin/core_r2xsql.${ext}" dist/plugin/plugins/
|
||||
cp LICENSE dist/plugin/
|
||||
libxsql_dir=${LIBXSQL_DIR:-build/_deps/libxsql-src}
|
||||
cp "$libxsql_dir/LICENSE" dist/plugin/licenses/libxsql.txt
|
||||
cp "$libxsql_dir/external/nlohmann/LICENSE" dist/plugin/licenses/nlohmann-json.txt
|
||||
|
||||
# Check automatic discovery of the packaged plugin, including its suffix.
|
||||
result=$(R2_LIBR_PLUGINS="$PWD/dist/plugin/plugins" \
|
||||
LD_LIBRARY_PATH="$PWD/r2prefix/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||||
DYLD_LIBRARY_PATH="$PWD/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" \
|
||||
./r2prefix/bin/radare2 -Nq -c 'sqlj SELECT 1 AS ok' -)
|
||||
[[ "$result" == *'"rows":[["1"]]'* ]] || { echo "$result" >&2; exit 1; }
|
||||
# Test the packaged binary, automatic discovery and both process exit paths.
|
||||
R2_LIBR_PLUGINS="$PWD/dist/plugin/plugins" \
|
||||
python3 tests/smoke.py ./r2prefix/bin/radare2
|
||||
|
||||
cat > dist/plugin/README.txt <<EOF
|
||||
r2xsql — radare2 core plugin
|
||||
@@ -48,7 +55,7 @@ with double quotes to preserve SQL operators and statement separators.
|
||||
Analysis is explicit: run aaa or start radare2 with -A.
|
||||
|
||||
The plugin also works through radare2's own HTTP server:
|
||||
radare2 -e http.bind=127.0.0.1 -e http.port=9090 -c aaa -c '=h' <file>
|
||||
radare2 -e http.bind=127.0.0.1 -e http.sandbox=false -e http.port=9090 -c aaa -c '=h' <file>
|
||||
curl --data-binary '"sqlj SELECT name FROM funcs LIMIT 5"' http://127.0.0.1:9090/cmd/
|
||||
|
||||
Read the command output for errors even when HTTP returns status 200.
|
||||
|
||||
@@ -26,7 +26,8 @@ jobs:
|
||||
# ============================================================================
|
||||
build-linux:
|
||||
name: Build (Linux)
|
||||
runs-on: ubuntu-latest
|
||||
# Keep the release's glibc/libstdc++ baseline stable as ubuntu-latest moves.
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -171,6 +172,12 @@ jobs:
|
||||
-DRadare2_ROOT="$GITHUB_WORKSPACE/r2prefix"
|
||||
cmake --build build --config Release
|
||||
|
||||
- name: Run r2r regression tests
|
||||
run: |
|
||||
export PATH="$GITHUB_WORKSPACE/r2prefix/bin:$PATH"
|
||||
export DYLD_LIBRARY_PATH="$GITHUB_WORKSPACE/r2prefix/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}"
|
||||
make -C tests R2XSQL_PLUGIN="$GITHUB_WORKSPACE/build/bin/core_r2xsql.dylib"
|
||||
|
||||
- name: Stage artifacts
|
||||
id: stage
|
||||
shell: bash
|
||||
|
||||
+22
-11
@@ -22,7 +22,7 @@
|
||||
# Radare2::libr (INTERFACE bundle of the lot)
|
||||
#
|
||||
# Plus:
|
||||
# Radare2_EXECUTABLE — path to radare2(.exe) (for the r2pipe backend)
|
||||
# Radare2_EXECUTABLE — path to radare2(.exe)
|
||||
|
||||
set(_r2_search_roots "")
|
||||
|
||||
@@ -66,18 +66,28 @@ endif()
|
||||
if(NOT Radare2_FOUND)
|
||||
find_package(PkgConfig QUIET)
|
||||
if(PkgConfig_FOUND)
|
||||
pkg_check_modules(_R2 QUIET r_core)
|
||||
pkg_check_modules(_R2 QUIET IMPORTED_TARGET r_core)
|
||||
if(_R2_FOUND)
|
||||
set(Radare2_FOUND TRUE)
|
||||
pkg_get_variable(Radare2_ROOT r_core prefix)
|
||||
set(Radare2_VERSION "${_R2_VERSION}")
|
||||
set(Radare2_INCLUDE_DIRS "${_R2_INCLUDE_DIRS}")
|
||||
set(Radare2_LIBRARY_DIRS "${_R2_LIBRARY_DIRS}")
|
||||
set(Radare2_LIBRARIES "${_R2_LIBRARIES}")
|
||||
set(Radare2_LIBRARIES PkgConfig::_R2)
|
||||
# r_core.pc includes its public dependencies, including r_util. Keep
|
||||
# pkg-config's link flags and paths instead of guessing a libdir.
|
||||
foreach(_target libr r_util)
|
||||
if(NOT TARGET Radare2::${_target})
|
||||
add_library(Radare2::${_target} INTERFACE IMPORTED)
|
||||
set_target_properties(Radare2::${_target} PROPERTIES
|
||||
INTERFACE_LINK_LIBRARIES PkgConfig::_R2)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(Radare2_FOUND AND Radare2_ROOT)
|
||||
if(_r2_resolved_root)
|
||||
# Normalize separators: on Windows, Git-Bash passes -DRadare2_ROOT as
|
||||
# "D:\a\...\r2prefix" (backslashes), and file(GLOB) treats backslashes as escapes
|
||||
# rather than separators, so the libdir glob below matches nothing and we end up
|
||||
@@ -118,8 +128,8 @@ if(Radare2_FOUND AND Radare2_ROOT)
|
||||
endif()
|
||||
|
||||
set(_r2_libs r_core r_anal r_bin r_util r_io r_cons r_main r_socket
|
||||
r_reg r_arch r_search r_syscall r_config r_lang r_egg r_debug
|
||||
r_bp r_crypto r_flag r_fs r_magic r_parse)
|
||||
r_reg r_arch r_asm r_esil r_search r_syscall r_config r_lang r_egg r_debug
|
||||
r_bp r_crypto r_muta r_flag r_fs r_magic r_parse)
|
||||
set(Radare2_LIBRARIES "")
|
||||
foreach(_lib IN LISTS _r2_libs)
|
||||
set(_full "")
|
||||
@@ -148,10 +158,8 @@ if(Radare2_FOUND AND Radare2_ROOT)
|
||||
# r_core_* symbols. Surface it at configure time instead.
|
||||
list(LENGTH Radare2_LIBRARIES _r2_nlibs)
|
||||
message(STATUS "FindRadare2: libdir='${_r2_libdir}', resolved ${_r2_nlibs} libraries")
|
||||
if(_r2_nlibs EQUAL 0)
|
||||
message(WARNING "FindRadare2: found radare2 at '${Radare2_ROOT}' but located ZERO "
|
||||
"import libraries under '${_r2_libdir}'. Full/plugin links will fail "
|
||||
"with undefined r_core_* symbols.")
|
||||
if(NOT TARGET Radare2::r_core OR NOT TARGET Radare2::r_util)
|
||||
set(Radare2_LIBRARIES "")
|
||||
endif()
|
||||
|
||||
if(NOT TARGET Radare2::libr)
|
||||
@@ -161,6 +169,9 @@ if(Radare2_FOUND AND Radare2_ROOT)
|
||||
INTERFACE_LINK_LIBRARIES "${Radare2_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
if(Radare2_FOUND)
|
||||
find_program(Radare2_EXECUTABLE
|
||||
NAMES radare2 r2
|
||||
PATHS "${Radare2_ROOT}/bin"
|
||||
@@ -172,7 +183,7 @@ endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Radare2
|
||||
REQUIRED_VARS Radare2_ROOT Radare2_INCLUDE_DIRS
|
||||
REQUIRED_VARS Radare2_INCLUDE_DIRS Radare2_LIBRARIES
|
||||
VERSION_VAR Radare2_VERSION)
|
||||
|
||||
mark_as_advanced(Radare2_INCLUDE_DIRS Radare2_LIBRARIES Radare2_EXECUTABLE)
|
||||
+15
-3
@@ -42,7 +42,7 @@ make user-install # install into the per-user plugin dir (no root)
|
||||
make install # install system-wide (R2_LIBR_PLUGINS)
|
||||
make user-uninstall # remove the per-user plugin without building
|
||||
make uninstall # remove the system-wide plugin without building
|
||||
make test # load it in r2 and run one SQL query
|
||||
make test # run the command and HTTP regression suite
|
||||
# only `radare2` (not `r2`) on PATH? -> make R2=radare2
|
||||
```
|
||||
|
||||
@@ -65,12 +65,24 @@ sibling `../libxsql` checkout (else clones it), and meson pulls it as a
|
||||
subproject. All three builds pin the same libxsql release tag (`v1.0.21`).
|
||||
|
||||
When published, binary release assets are named `r2xsql-<radare2-version>-<R2PM_TRIPLET>.zip`, for
|
||||
example `r2xsql-6.2.4-linux-x86-64.zip`. Each contains the plugin in `plugins/`.
|
||||
With a recent r2pm supporting binary installs, use `r2pm -U` followed by
|
||||
example `r2xsql-6.2.6-linux-x86-64.zip`. The first release, `v0.0.1`, targets
|
||||
radare2 **6.2.6** on Linux x86-64, Windows x86-64 and macOS ARM64. The r2pm
|
||||
package uses `R2PM_VERSION_MAP "6.2.6:v0.0.1"` to select this release.
|
||||
Each archive contains the plugin in `plugins/` and dependency license notices.
|
||||
With r2pm 6.2.6 or newer, use `r2pm -U` followed by
|
||||
`r2pm -bi r2xsql`; uninstall with `r2pm -u r2xsql`. Only matching radare2
|
||||
versions and targets are installed. To install manually, copy the plugin to
|
||||
the directory printed by `radare2 -NN -H R2_USER_PLUGINS`.
|
||||
|
||||
Windows binaries use the dynamic MSVC runtime, matching radare2's MSVC build.
|
||||
Use the matching radare2 distribution and its runtime dependencies; the plugin
|
||||
archive does not bundle another copy of radare2's DLLs.
|
||||
|
||||
Tag the release as `v0.0.1` after the build workflow passes. Tag builds publish
|
||||
the three ZIPs only after every platform succeeds. CI tests automatic plugin
|
||||
discovery, JSON/CSV output, comment writes, error recovery, and normal/quick
|
||||
process exit on all three platforms, plus the r2r suite on Linux and macOS.
|
||||
|
||||
## Tests
|
||||
|
||||
Run the regression suite with radare2, `r2r`, a C compiler and Python 3 on
|
||||
|
||||
@@ -140,7 +140,10 @@ void build_data_items(Backend* be, std::vector<DataItemRow>& rows) {
|
||||
r.has_value = !r.value_repr.empty();
|
||||
r.segment_name = j.section;
|
||||
r.has_segment = !r.segment_name.empty();
|
||||
if (!r.has_size) { r.size = j.size; r.has_size = true; }
|
||||
// Mach-O symbols and ELF mapping symbols (e.g. ARM's $d) can have
|
||||
// unknown/zero size. Preserve a known symbol size, otherwise use the
|
||||
// string's byte size, including its terminator.
|
||||
if (!r.has_size || r.size <= 0) { r.size = j.size; r.has_size = true; }
|
||||
}
|
||||
|
||||
rows.reserve(by_addr.size());
|
||||
|
||||
+37
-33
@@ -17,8 +17,8 @@
|
||||
// sql.views list available views
|
||||
//
|
||||
// The plugin wraps the host RCore* with libr2xsql's LibrBackend (no
|
||||
// ownership) and shares a single Session across calls so SQLite caches
|
||||
// stay warm across queries.
|
||||
// ownership) and keeps one Session per core across calls so SQLite caches
|
||||
// stay warm across queries. radare2 owns its lifetime through ctx->data.
|
||||
|
||||
#include <r2xsql/r2xsql.hpp>
|
||||
#include <xsql/query_script.hpp>
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <new>
|
||||
#include <string>
|
||||
|
||||
// r2xsql::make_libr_backend_from_core (declared in <r2xsql/backend.hpp>, included
|
||||
@@ -39,18 +40,16 @@ namespace {
|
||||
|
||||
struct PluginState {
|
||||
std::unique_ptr<r2xsql::Session> session;
|
||||
RCore* bound_core = nullptr;
|
||||
std::mutex mu;
|
||||
};
|
||||
|
||||
PluginState g_state;
|
||||
|
||||
void ensure_session(RCore* core) {
|
||||
std::lock_guard<std::mutex> lock(g_state.mu);
|
||||
if (g_state.session && g_state.bound_core == core) return;
|
||||
// Called with state.mu held. Do not keep a namespace-scope owning Session:
|
||||
// quick exit skips plugin_fini, and static destruction can close SQLite after
|
||||
// libxsql's cache registry has already been destroyed.
|
||||
void ensure_session(PluginState& state, RCore* core) {
|
||||
if (state.session) return;
|
||||
auto backend = r2xsql::make_libr_backend_from_core(core);
|
||||
g_state.session = std::make_unique<r2xsql::Session>(std::move(backend));
|
||||
g_state.bound_core = core;
|
||||
state.session = std::make_unique<r2xsql::Session>(std::move(backend));
|
||||
}
|
||||
|
||||
static RCoreHelpMessage help_msg_sql = {
|
||||
@@ -106,7 +105,7 @@ std::string render_table(RCore* core, const xsql::Result& res,
|
||||
return out ? out.get() : "";
|
||||
}
|
||||
|
||||
bool dispatch(RCore* core, const char* input) {
|
||||
bool dispatch(PluginState& state, RCore* core, const char* input) {
|
||||
if (!input) return false;
|
||||
|
||||
std::string table_query;
|
||||
@@ -151,15 +150,12 @@ bool dispatch(RCore* core, const char* input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ensure_session(core);
|
||||
|
||||
std::string out;
|
||||
{
|
||||
// Serialize against concurrent callers (e.g. from other plugins or tools)
|
||||
// which also touch g_state.session / SQLite under this same mutex.
|
||||
std::lock_guard<std::mutex> lock(g_state.mu);
|
||||
std::lock_guard<std::mutex> lock(state.mu);
|
||||
try {
|
||||
auto res = g_state.session->query(input);
|
||||
ensure_session(state, core);
|
||||
auto res = state.session->query(input);
|
||||
out = render_table(core, res, table_query.c_str());
|
||||
} catch (const std::exception& e) {
|
||||
out = std::string("EXCEPTION: ") + e.what() + "\n";
|
||||
@@ -171,7 +167,7 @@ bool dispatch(RCore* core, const char* input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dispatch_json(RCore* core, const char* input) {
|
||||
bool dispatch_json(PluginState& state, RCore* core, const char* input) {
|
||||
input = r_str_trim_head_ro(input);
|
||||
if (!*input || std::strcmp(input, "?") == 0) {
|
||||
print_help(core);
|
||||
@@ -179,10 +175,10 @@ bool dispatch_json(RCore* core, const char* input) {
|
||||
}
|
||||
std::string out;
|
||||
try {
|
||||
ensure_session(core);
|
||||
std::lock_guard<std::mutex> lock(g_state.mu);
|
||||
auto exec = [](const std::string& sql, xsql::ScriptStatementResult& result) {
|
||||
auto res = g_state.session->query(sql);
|
||||
std::lock_guard<std::mutex> lock(state.mu);
|
||||
ensure_session(state, core);
|
||||
auto exec = [&state](const std::string& sql, xsql::ScriptStatementResult& result) {
|
||||
auto res = state.session->query(sql);
|
||||
result.success = res.ok();
|
||||
result.error = res.error;
|
||||
result.warnings = std::move(res.warnings);
|
||||
@@ -205,11 +201,12 @@ bool dispatch_json(RCore* core, const char* input) {
|
||||
}
|
||||
|
||||
bool plugin_call(RCorePluginSession* ctx, const char* input) {
|
||||
if (!ctx || !input) return false;
|
||||
if (!ctx || !ctx->data || !input) return false;
|
||||
auto& state = *static_cast<PluginState*>(ctx->data);
|
||||
if (std::strncmp(input, "sqlj", 4) == 0) {
|
||||
const char* sql = input + 4;
|
||||
if (*sql && *sql != ' ' && *sql != '\t' && *sql != '?') return false;
|
||||
return dispatch_json(ctx->core, sql);
|
||||
return dispatch_json(state, ctx->core, sql);
|
||||
}
|
||||
// r2 calls every registered plugin's call() for every command; only
|
||||
// claim the ones that start with our prefix.
|
||||
@@ -219,17 +216,24 @@ bool plugin_call(RCorePluginSession* ctx, const char* input) {
|
||||
if (*rest != '\0' && *rest != ' ' && *rest != '?' && *rest != '.' && *rest != ',') {
|
||||
return false;
|
||||
}
|
||||
return dispatch(ctx->core, rest);
|
||||
return dispatch(state, ctx->core, rest);
|
||||
}
|
||||
|
||||
bool plugin_init(RCorePluginSession* /*ctx*/) {
|
||||
return true;
|
||||
bool plugin_init(RCorePluginSession* ctx) {
|
||||
ctx->data = new (std::nothrow) PluginState;
|
||||
return ctx->data != nullptr;
|
||||
}
|
||||
|
||||
bool plugin_fini(RCorePluginSession* /*ctx*/) {
|
||||
std::lock_guard<std::mutex> lock(g_state.mu);
|
||||
g_state.session.reset();
|
||||
g_state.bound_core = nullptr;
|
||||
bool plugin_fini(RCorePluginSession* ctx) {
|
||||
auto* state = static_cast<PluginState*>(ctx->data);
|
||||
if (state) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(state->mu);
|
||||
state->session.reset();
|
||||
}
|
||||
delete state;
|
||||
ctx->data = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -242,10 +246,10 @@ static RCorePlugin r_core_plugin_r2xsql = {
|
||||
/*.name =*/ const_cast<char*>("r2xsql"),
|
||||
/*.desc =*/ const_cast<char*>("SQL interface for radare2 (libr2xsql)"),
|
||||
/*.author =*/ const_cast<char*>("0xeb"),
|
||||
/*.version =*/ const_cast<char*>("0.0.1"),
|
||||
/*.version =*/ const_cast<char*>(R2XSQL_VERSION_STRING),
|
||||
/*.license =*/ const_cast<char*>("LicenseRef-Human-Origin-Source-1.0"),
|
||||
/*.contact =*/ const_cast<char*>("[email protected]"),
|
||||
/*.copyright =*/ const_cast<char*>("Copyright (c) 2024-2026 Elias Bachaalany"),
|
||||
/*.copyright =*/ const_cast<char*>(R2XSQL_COPYRIGHT_STRING),
|
||||
/*.status =*/ R_PLUGIN_STATUS_BASIC,
|
||||
},
|
||||
/*.init =*/ plugin_init,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
NAME=native IO maps keep metacharacters in names as data
|
||||
NAME=native IO maps sanitize names without executing metacharacters
|
||||
FILE=malloc://64
|
||||
ARGS=-e log.level=0 -i build/load.r2
|
||||
CMDS=<<CMDS
|
||||
'sql,:quiet INSERT INTO io_maps(start_addr,end_addr,paddr,perm,name) VALUES(4096,4112,4,'r-x','map ; f map_injected $(f map_subst) ''quote'' # tail' || char(10) || 'f map_newline')
|
||||
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096 AND end_addr=4112 AND paddr=4 AND perm='r-x' AND name='map ; f map_injected $(f map_subst) ''quote'' # tail' || char(10) || 'f map_newline'
|
||||
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096 AND end_addr=4112 AND paddr=4 AND perm='r-x' AND name LIKE 'map%map_injected%map_subst%quote%tail%map_newline'
|
||||
'sql,:quiet SELECT count(*) FROM flags WHERE name IN ('map_injected','map_subst','map_newline')
|
||||
'sql,:quiet DELETE FROM io_maps WHERE start_addr=4096
|
||||
'sql,:quiet SELECT count(*) FROM io_maps WHERE start_addr=4096
|
||||
|
||||
@@ -3,7 +3,7 @@ FILE=build/fixture.bin
|
||||
ARGS=-e log.level=0 -i build/load.r2
|
||||
CMDS=<<CMDS
|
||||
aaa
|
||||
"sql,:quiet SELECT count(*)=1 FROM data_items WHERE name='r2xsql_marker' AND size>0 AND is_string=0 AND is_initialized=1"
|
||||
"sql,:quiet SELECT count(*)=1 FROM data_items WHERE name IN ('r2xsql_marker','_r2xsql_marker') AND is_string=0 AND is_initialized=1"
|
||||
"sql,:quiet SELECT count(*)>0 FROM data_items WHERE is_string=1 AND value_repr='r2xsql_test_marker_string' AND size>length(value_repr)"
|
||||
"sql,:quiet SELECT (SELECT count(*) FROM grep WHERE kind='func')=(SELECT count(*) FROM funcs)"
|
||||
"sql,:quiet SELECT (SELECT count(*) FROM grep WHERE kind='import')=(SELECT count(*) FROM imports)"
|
||||
|
||||
@@ -31,7 +31,7 @@ def test_r2_http(r2_bin, r2xsql_plugin, fixture_binary, tmp_path, post):
|
||||
with log_path.open("w") as log:
|
||||
proc = subprocess.Popen([
|
||||
r2_bin, "-NN", "-q", "-e", "http.bind=127.0.0.1", "-e", f"http.port={port}",
|
||||
"-e", "http.log=false", "-e", "scr.color=0",
|
||||
"-e", "http.log=false", "-e", "http.sandbox=false", "-e", "scr.color=0",
|
||||
"-c", f"L {r2xsql_plugin}", "-c", "aa", "-c", "=h", str(fixture_binary),
|
||||
], stdout=log, stderr=log, cwd=tmp_path)
|
||||
try:
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# SPDX-License-Identifier: LicenseRef-Human-Origin-Source-1.0
|
||||
"""Check an automatically discovered release plugin on every supported OS."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def check(r2):
|
||||
# -Q skips plugin finalizers; process teardown must still be safe.
|
||||
for quiet in ("-q", "-Q"):
|
||||
commands = [
|
||||
'"sqlj SELECT NULL AS missing, 7 > 3 AS ok; SELECT 2 AS n"',
|
||||
'"sqlj INSERT INTO comments(addr,text) VALUES(4, \'release smoke\'); '
|
||||
"SELECT text FROM comments WHERE addr=4; "
|
||||
"DELETE FROM comments WHERE addr=4; "
|
||||
'SELECT count(*) FROM comments WHERE addr=4"',
|
||||
'"sqlj SELECT * FROM missing_table"',
|
||||
'"sqlj SELECT 42 AS alive"',
|
||||
'"sql,:csv SELECT 1 AS n, \'a,b\' AS name"',
|
||||
]
|
||||
args = [r2, "-N", quiet, "-e", "scr.color=0", "-e", "scr.utf8=false"]
|
||||
for command in commands:
|
||||
args.extend(["-c", command])
|
||||
result = subprocess.run(args + ["malloc://64"], capture_output=True,
|
||||
text=True, timeout=30)
|
||||
assert result.returncode == 0, (quiet, result.returncode, result.stderr)
|
||||
lines = result.stdout.splitlines()
|
||||
assert len(lines) == 6, (result.stdout, result.stderr)
|
||||
multi, write, error, alive = map(json.loads, lines[:4])
|
||||
assert multi["success"] and multi["statement_count"] == 2, multi
|
||||
assert multi["results"][0]["rows"] == [[None, "1"]], multi
|
||||
assert multi["results"][1]["rows"] == [["2"]], multi
|
||||
assert write["success"] and write["statement_count"] == 4, write
|
||||
assert write["results"][1]["rows"] == [["release smoke"]], write
|
||||
assert write["results"][3]["rows"] == [["0"]], write
|
||||
assert not error["success"], error
|
||||
assert alive["success"] and alive["results"][0]["rows"] == [["42"]], alive
|
||||
assert lines[4:] == ["n,name", '1,"a,b"'], result.stdout
|
||||
print("Packaged plugin: JSON, CRUD, errors, CSV and process cleanup OK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
check(str(Path(sys.argv[1]).resolve()))
|
||||
Reference in new issue
Block a user