extralite 3.1.2 → 3.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/test-asan.yml +37 -0
- data/.gitignore +1 -1
- data/CHANGELOG.md +9 -0
- data/Rakefile +32 -0
- data/ext/extralite/database.c +17 -19
- data/ext/extralite/extconf-bundle.rb +18 -0
- data/ext/extralite/extconf.rb +15 -0
- data/ext/extralite/iterator.c +1 -1
- data/ext/extralite/query.c +6 -6
- data/lib/extralite/version.rb +1 -1
- data/lib/extralite.rb +1 -1
- data/test/helper.rb +37 -0
- data/test/test_database.rb +23 -17
- data/test/test_iterator.rb +1 -1
- data/test/test_query.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5281aeb88afdc13d5ab8997ef3d268b5c2f23bba5c10121d82d93fa1b0c6683e
|
|
4
|
+
data.tar.gz: 98c7ebcc643f1c228e7dd154530363d2b590c32e8c6ec2048ab680e6ef8dff29
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 500b76bae6913d602bfea5a611e0dab55a36563b665afc9ad48143c379cd6c7cfffa768a35dfaec966286197dbc1e1bdf02a4d7203bab6f3adf4b10c14ff92e3
|
|
7
|
+
data.tar.gz: d93b59540dfd75a7ddb1d0477e48cf064d49e1c8800f18b7953f7a04e58a756c48f6e3dac71e6d3d8df4463faa3a573aafb942bbf3e93170a630e5dcaf9dad12
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Tests (AddressSanitizer)
|
|
2
|
+
|
|
3
|
+
on: [push, pull_request]
|
|
4
|
+
|
|
5
|
+
concurrency:
|
|
6
|
+
group: tests-${{ format('{0}-{1}', github.head_ref || github.run_number, github.job) }}
|
|
7
|
+
cancel-in-progress: true
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
asan:
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
include:
|
|
15
|
+
# Ruby is not instrumented, rake test:asan preloads libasan
|
|
16
|
+
- { ruby: '3.4', os: ubuntu-latest, test_task: 'test:asan' }
|
|
17
|
+
- { ruby: '4.0', os: ubuntu-latest, test_task: 'test:asan' }
|
|
18
|
+
# Ruby built with ASan (latest stable release), only on ubuntu-24.04
|
|
19
|
+
- { ruby: 'asan-release', os: ubuntu-24.04, test_task: 'test' }
|
|
20
|
+
|
|
21
|
+
name: ASan, ${{matrix.ruby}}
|
|
22
|
+
|
|
23
|
+
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
24
|
+
|
|
25
|
+
runs-on: ${{matrix.os}}
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: ruby/setup-ruby@v1
|
|
29
|
+
with:
|
|
30
|
+
ruby-version: ${{matrix.ruby}}
|
|
31
|
+
bundler-cache: true
|
|
32
|
+
- name: Compile C-extension with ASan (bundled SQLite)
|
|
33
|
+
run: EXTRALITE_BUNDLE=1 EXTRALITE_ASAN=1 bundle exec rake compile
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: bundle exec rake ${{matrix.test_task}}
|
|
36
|
+
- name: Run tests with GC.stress
|
|
37
|
+
run: EXTRALITE_GC_STRESS=1 bundle exec rake ${{matrix.test_task}}
|
data/.gitignore
CHANGED
|
@@ -51,7 +51,7 @@ build-iPhoneSimulator/
|
|
|
51
51
|
# for a library or gem, you might want to ignore these files since the code is
|
|
52
52
|
# intended to run in multiple environments; otherwise, check them in:
|
|
53
53
|
# Gemfile.lock
|
|
54
|
-
|
|
54
|
+
.ruby-version
|
|
55
55
|
# .ruby-gemset
|
|
56
56
|
|
|
57
57
|
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
# 3.1.4 2026-11-07
|
|
2
|
+
|
|
3
|
+
- Fix loading of extension on some systems running Ruby from distro packages (#96)
|
|
4
|
+
|
|
5
|
+
# 3.1.3 2026-10-03
|
|
6
|
+
|
|
7
|
+
- Fix database finalization of stmts (on free/close)
|
|
8
|
+
- Use stmt_cache for storing Query stmts
|
|
9
|
+
|
|
1
10
|
# 3.1.2 2026-10-03
|
|
2
11
|
|
|
3
12
|
- Fix handling of failed BEGIN in #transaction (#89)
|
data/Rakefile
CHANGED
|
@@ -15,6 +15,38 @@ task :test do
|
|
|
15
15
|
exec 'ruby test/run.rb'
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
namespace :test do
|
|
19
|
+
# Runs the tests against an extension built with AddressSanitizer:
|
|
20
|
+
#
|
|
21
|
+
# bundle exec rake clean
|
|
22
|
+
# EXTRALITE_BUNDLE=1 EXTRALITE_ASAN=1 bundle exec rake compile
|
|
23
|
+
# bundle exec rake test:asan
|
|
24
|
+
#
|
|
25
|
+
# Set TEST_FILES (comma separated) to run only some test files.
|
|
26
|
+
#
|
|
27
|
+
# Ruby itself is not built with ASan, so libasan has to be preloaded.
|
|
28
|
+
# use_sigaltstack=0: Ruby installs its own signal stack for each thread,
|
|
29
|
+
# which makes ASan fail when it unmaps its own signal stack on thread exit.
|
|
30
|
+
# Leak detection is off because Ruby does not free everything on exit.
|
|
31
|
+
desc 'Run tests with an ASan build of the extension (Linux only)'
|
|
32
|
+
task :asan do
|
|
33
|
+
so = Dir['lib/extralite_ext.{so,bundle}'].first
|
|
34
|
+
libasan = so && `ldd #{so}`[/^\s*libasan\S*\s*=>\s*(\S+)/, 1]
|
|
35
|
+
abort 'The extension is not built with EXTRALITE_ASAN=1' unless libasan
|
|
36
|
+
|
|
37
|
+
env = {
|
|
38
|
+
'LD_PRELOAD' => libasan,
|
|
39
|
+
'ASAN_OPTIONS' => "detect_leaks=0:use_sigaltstack=0:abort_on_error=1:#{ENV['ASAN_OPTIONS']}"
|
|
40
|
+
}
|
|
41
|
+
files = ENV['TEST_FILES']&.split(',')
|
|
42
|
+
if files
|
|
43
|
+
exec env, 'ruby', '-Itest', '-e', files.map { "require './#{it}'" }.join(';')
|
|
44
|
+
else
|
|
45
|
+
exec env, 'ruby test/run.rb'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
18
50
|
CLEAN.include 'lib/*.o', 'lib/*.so', 'lib/*.so.*', 'lib/*.a', 'lib/*.bundle', 'lib/*.jar', 'pkg', 'tmp'
|
|
19
51
|
|
|
20
52
|
require 'yard'
|
data/ext/extralite/database.c
CHANGED
|
@@ -72,24 +72,19 @@ static void Database_compact(void *ptr) {
|
|
|
72
72
|
db->progress_handler.proc = rb_gc_location(db->progress_handler.proc);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
static
|
|
76
|
-
sqlite3_stmt *stmt = (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (stmt_cache == Qnil) return;
|
|
83
|
-
if (rb_hash_size_num(stmt_cache) == 0) return;
|
|
84
|
-
|
|
85
|
-
rb_hash_foreach(stmt_cache, stmt_cache_iter, Qnil);
|
|
86
|
-
RB_GC_GUARD(stmt_cache);
|
|
75
|
+
static inline void finalize_all_stmts(sqlite3 *db) {
|
|
76
|
+
sqlite3_stmt *stmt = sqlite3_next_stmt(db, NULL);
|
|
77
|
+
while (stmt) {
|
|
78
|
+
sqlite3_stmt *next = sqlite3_next_stmt(db, stmt);
|
|
79
|
+
sqlite3_finalize(stmt);
|
|
80
|
+
stmt = next;
|
|
81
|
+
}
|
|
87
82
|
}
|
|
88
83
|
|
|
89
84
|
static void Database_free(void *ptr) {
|
|
90
85
|
Database_t *db = ptr;
|
|
91
86
|
if (db->sqlite3_db) {
|
|
92
|
-
|
|
87
|
+
finalize_all_stmts(db->sqlite3_db);
|
|
93
88
|
sqlite3_close_v2(db->sqlite3_db);
|
|
94
89
|
}
|
|
95
90
|
free(ptr);
|
|
@@ -242,11 +237,12 @@ VALUE Database_initialize(int argc, VALUE *argv, VALUE self) {
|
|
|
242
237
|
sqlite3_db_config(db->sqlite3_db ,SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION, 1, NULL);
|
|
243
238
|
#endif
|
|
244
239
|
|
|
245
|
-
db->stmt_cache
|
|
240
|
+
RB_OBJ_WRITE(self, &db->stmt_cache, rb_hash_new());
|
|
246
241
|
db->trace_proc = Qnil;
|
|
247
242
|
db->gvl_release_threshold = DEFAULT_GVL_RELEASE_THRESHOLD;
|
|
248
243
|
|
|
249
244
|
db->progress_handler = global_progress_handler;
|
|
245
|
+
RB_OBJ_WRITTEN(self, Qundef, db->progress_handler.proc);
|
|
250
246
|
db->progress_handler.tick_count = 0;
|
|
251
247
|
db->progress_handler.call_count = 0;
|
|
252
248
|
if (db->progress_handler.mode != PROGRESS_NONE) {
|
|
@@ -278,7 +274,7 @@ VALUE Database_close(VALUE self) {
|
|
|
278
274
|
int rc;
|
|
279
275
|
Database_t *db = self_to_database(self);
|
|
280
276
|
|
|
281
|
-
|
|
277
|
+
finalize_all_stmts(db->sqlite3_db);
|
|
282
278
|
rb_hash_clear(db->stmt_cache);
|
|
283
279
|
rc = sqlite3_close_v2(db->sqlite3_db);
|
|
284
280
|
if (rc) {
|
|
@@ -632,7 +628,7 @@ VALUE Database_batch_query(VALUE self, VALUE sql, VALUE parameters) {
|
|
|
632
628
|
sqlite3_stmt *stmt;
|
|
633
629
|
|
|
634
630
|
stmt_ctx stmt_ctx;
|
|
635
|
-
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, RARRAY_LEN(parameters), NULL);
|
|
631
|
+
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, (TYPE(parameters) == T_ARRAY) ? RARRAY_LEN(parameters) : 0, NULL);
|
|
636
632
|
prep_single_stmt(&stmt_ctx);
|
|
637
633
|
// prepare_xsingle_stmt(DB_GVL_MODE(db), db->stmt_cache, db->sqlite3_db, &stmt, sql, 0, NULL);
|
|
638
634
|
query_ctx ctx = QUERY_CTX(
|
|
@@ -669,7 +665,7 @@ VALUE Database_batch_query_array(VALUE self, VALUE sql, VALUE parameters) {
|
|
|
669
665
|
sqlite3_stmt *stmt;
|
|
670
666
|
|
|
671
667
|
stmt_ctx stmt_ctx;
|
|
672
|
-
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, RARRAY_LEN(parameters), NULL);
|
|
668
|
+
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, (TYPE(parameters) == T_ARRAY) ? RARRAY_LEN(parameters) : 0, NULL);
|
|
673
669
|
prep_single_stmt(&stmt_ctx);
|
|
674
670
|
// prepare_xsingle_stmt(DB_GVL_MODE(db), db->stmt_cache, db->sqlite3_db, &stmt, sql, 0, NULL);
|
|
675
671
|
query_ctx ctx = QUERY_CTX(
|
|
@@ -706,7 +702,7 @@ VALUE Database_batch_query_splat(VALUE self, VALUE sql, VALUE parameters) {
|
|
|
706
702
|
sqlite3_stmt *stmt;
|
|
707
703
|
|
|
708
704
|
stmt_ctx stmt_ctx;
|
|
709
|
-
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, RARRAY_LEN(parameters), NULL);
|
|
705
|
+
make_stmt_ctx(&stmt_ctx, db, &stmt, sql, (TYPE(parameters) == T_ARRAY) ? RARRAY_LEN(parameters) : 0, NULL);
|
|
710
706
|
prep_single_stmt(&stmt_ctx);
|
|
711
707
|
// prepare_xsingle_stmt(DB_GVL_MODE(db), db->stmt_cache, db->sqlite3_db, &stmt, sql, 0, NULL);
|
|
712
708
|
query_ctx ctx = QUERY_CTX(
|
|
@@ -1741,7 +1737,9 @@ void Init_ExtraliteDatabase(void) {
|
|
|
1741
1737
|
rb_gc_register_mark_object(SYM_truncate);
|
|
1742
1738
|
rb_gc_register_mark_object(SYM_wal);
|
|
1743
1739
|
|
|
1744
|
-
|
|
1740
|
+
// the proc is replaced by Extralite.on_progress, so register the address,
|
|
1741
|
+
// not the current value
|
|
1742
|
+
rb_gc_register_address(&global_progress_handler.proc);
|
|
1745
1743
|
|
|
1746
1744
|
UTF8_ENCODING = rb_utf8_encoding();
|
|
1747
1745
|
}
|
|
@@ -6,6 +6,24 @@ $CFLAGS << ' -Wno-undef'
|
|
|
6
6
|
$CFLAGS << ' -Wno-discarded-qualifiers'
|
|
7
7
|
$CFLAGS << ' -Wno-unused-function'
|
|
8
8
|
|
|
9
|
+
# Development only: build with AddressSanitizer (EXTRALITE_ASAN=1), then run
|
|
10
|
+
# the tests with `bundle exec rake test:asan` (see Rakefile).
|
|
11
|
+
#
|
|
12
|
+
# -fgnu89-inline: several non-static functions in the C files are declared
|
|
13
|
+
# `inline` without an extern definition, which only links when every call is
|
|
14
|
+
# inlined (as with the default -O3).
|
|
15
|
+
if ENV['EXTRALITE_ASAN']
|
|
16
|
+
$CFLAGS << ' -fsanitize=address -fno-omit-frame-pointer -O1 -g -fgnu89-inline'
|
|
17
|
+
$LDFLAGS << ' -fsanitize=address'
|
|
18
|
+
# No stack instrumentation: Ruby raises exceptions with longjmp, which
|
|
19
|
+
# leaves poisoned stack redzones behind and causes false reports later.
|
|
20
|
+
# It also keeps local VALUEs on the real stack, where the GC can see them.
|
|
21
|
+
$CFLAGS << (try_cflags('--param=asan-stack=0') ? ' --param=asan-stack=0' : ' -mllvm -asan-stack=0')
|
|
22
|
+
# SQLite serves small allocations (including statements) from its own
|
|
23
|
+
# lookaside pool, which hides use-after-free bugs from ASan.
|
|
24
|
+
$defs << '-DSQLITE_DEFAULT_LOOKASIDE=0,0'
|
|
25
|
+
end
|
|
26
|
+
|
|
9
27
|
# enable the session extension
|
|
10
28
|
$defs << '-DSQLITE_ENABLE_SESSION'
|
|
11
29
|
$defs << '-DSQLITE_ENABLE_PREUPDATE_HOOK'
|
data/ext/extralite/extconf.rb
CHANGED
|
@@ -48,6 +48,21 @@ else
|
|
|
48
48
|
dir_config('sqlite3', cppflags, ldflags)
|
|
49
49
|
end
|
|
50
50
|
|
|
51
|
+
# Development only: build with AddressSanitizer (EXTRALITE_ASAN=1), then run
|
|
52
|
+
# the tests with `bundle exec rake test:asan` (see Rakefile).
|
|
53
|
+
#
|
|
54
|
+
# -fgnu89-inline: several non-static functions in the C files are declared
|
|
55
|
+
# `inline` without an extern definition, which only links when every call is
|
|
56
|
+
# inlined (as with the default -O3).
|
|
57
|
+
if ENV['EXTRALITE_ASAN']
|
|
58
|
+
$CFLAGS << ' -fsanitize=address -fno-omit-frame-pointer -O1 -g -fgnu89-inline'
|
|
59
|
+
$LDFLAGS << ' -fsanitize=address'
|
|
60
|
+
# No stack instrumentation: Ruby raises exceptions with longjmp, which
|
|
61
|
+
# leaves poisoned stack redzones behind and causes false reports later.
|
|
62
|
+
# It also keeps local VALUEs on the real stack, where the GC can see them.
|
|
63
|
+
$CFLAGS << (try_cflags('--param=asan-stack=0') ? ' --param=asan-stack=0' : ' -mllvm -asan-stack=0')
|
|
64
|
+
end
|
|
65
|
+
|
|
51
66
|
if RbConfig::CONFIG['host_os'] =~ /mswin/
|
|
52
67
|
$CFLAGS << ' -W3'
|
|
53
68
|
end
|
data/ext/extralite/iterator.c
CHANGED
|
@@ -54,7 +54,7 @@ static inline Iterator_t *self_to_iterator(VALUE obj) {
|
|
|
54
54
|
*/
|
|
55
55
|
VALUE Iterator_initialize(VALUE self, VALUE query) {
|
|
56
56
|
Iterator_t *iterator = self_to_iterator(self);
|
|
57
|
-
iterator->query
|
|
57
|
+
RB_OBJ_WRITE(self, &iterator->query, query);
|
|
58
58
|
return Qnil;
|
|
59
59
|
}
|
|
60
60
|
|
data/ext/extralite/query.c
CHANGED
|
@@ -40,8 +40,7 @@ static void Query_compact(void *ptr) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
static void Query_free(void *ptr) {
|
|
43
|
-
Query_t *query = ptr;
|
|
44
|
-
if (query->stmt) sqlite3_finalize(query->stmt);
|
|
43
|
+
// Query_t *query = ptr;
|
|
45
44
|
free(ptr);
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -70,7 +69,7 @@ static inline Query_t *self_to_query(VALUE obj) {
|
|
|
70
69
|
// verifies that the query is not closed
|
|
71
70
|
static inline Query_t *self_to_query_verify(VALUE obj) {
|
|
72
71
|
Query_t *query = self_to_query(obj);
|
|
73
|
-
if (query->closed) rb_raise(cError, "Query is closed");
|
|
72
|
+
if (query->closed || !(query->db_struct->sqlite3_db)) rb_raise(cError, "Query is closed");
|
|
74
73
|
return query;
|
|
75
74
|
}
|
|
76
75
|
|
|
@@ -133,8 +132,10 @@ VALUE Query_initialize(VALUE self, VALUE db, VALUE sql, VALUE mode) {
|
|
|
133
132
|
|
|
134
133
|
static inline void prep_query(Query_t *query) {
|
|
135
134
|
stmt_ctx stmt_ctx;
|
|
136
|
-
make_stmt_ctx(&stmt_ctx, query->db_struct, &(query->stmt), query->sql,
|
|
135
|
+
make_stmt_ctx(&stmt_ctx, query->db_struct, &(query->stmt), query->sql, 1, NULL);
|
|
137
136
|
prep_single_stmt(&stmt_ctx);
|
|
137
|
+
if (!(stmt_ctx.flags & STMT_CTX_F_CACHE_HIT))
|
|
138
|
+
rb_hash_aset(query->db_struct->stmt_cache, query->sql, ULONG2NUM((uint64_t)(query->stmt)));
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
static inline void query_reset(Query_t *query) {
|
|
@@ -531,7 +532,6 @@ VALUE Query_clone(VALUE self) {
|
|
|
531
532
|
VALUE Query_close(VALUE self) {
|
|
532
533
|
Query_t *query = self_to_query(self);
|
|
533
534
|
if (query->stmt) {
|
|
534
|
-
sqlite3_finalize(query->stmt);
|
|
535
535
|
query->stmt = NULL;
|
|
536
536
|
}
|
|
537
537
|
query->closed = 1;
|
|
@@ -649,7 +649,7 @@ VALUE Query_transform_get(VALUE self) {
|
|
|
649
649
|
*/
|
|
650
650
|
VALUE Query_transform_set(VALUE self, VALUE transform) {
|
|
651
651
|
Query_t *query = self_to_query(self);
|
|
652
|
-
query->transform
|
|
652
|
+
RB_OBJ_WRITE(self, &query->transform, transform);
|
|
653
653
|
query->transform_object = rb_obj_is_instance_of(transform, cTransform);
|
|
654
654
|
return self;
|
|
655
655
|
}
|
data/lib/extralite/version.rb
CHANGED
data/lib/extralite.rb
CHANGED
data/test/helper.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require 'bundler/setup'
|
|
4
4
|
require 'extralite'
|
|
5
5
|
require 'minitest/autorun'
|
|
6
|
+
require 'tempfile'
|
|
6
7
|
|
|
7
8
|
puts "sqlite3 version: #{Extralite.sqlite3_version}"
|
|
8
9
|
|
|
@@ -22,7 +23,43 @@ module TestExtensions
|
|
|
22
23
|
@db = nil
|
|
23
24
|
GC.start
|
|
24
25
|
super
|
|
26
|
+
ensure
|
|
27
|
+
@tempfiles&.each(&:close!)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the path of a new temp file. The Tempfile is kept until teardown,
|
|
31
|
+
# otherwise GC may delete the file while a database is still using it.
|
|
32
|
+
def tempfile_path(name)
|
|
33
|
+
(@tempfiles ||= []) << (tmp = Tempfile.new(name))
|
|
34
|
+
tmp.path
|
|
25
35
|
end
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
Minitest::Test.prepend(TestExtensions)
|
|
39
|
+
|
|
40
|
+
# EXTRALITE_GC_STRESS=1 runs every test with GC.stress enabled, which makes GC
|
|
41
|
+
# bugs in the C extension (objects freed too early, wrong free order) show up
|
|
42
|
+
# reliably. It's slow, so you may want to run only some test files, e.g.:
|
|
43
|
+
#
|
|
44
|
+
# EXTRALITE_GC_STRESS=1 ruby test/test_stmt_cache.rb
|
|
45
|
+
if ENV['EXTRALITE_GC_STRESS']
|
|
46
|
+
module GCStressExtensions
|
|
47
|
+
def before_setup
|
|
48
|
+
super
|
|
49
|
+
GC.stress = true
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def after_teardown
|
|
53
|
+
GC.stress = false
|
|
54
|
+
super
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
Minitest::Test.prepend(GCStressExtensions)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
module Minitest::Assertions
|
|
62
|
+
def skip_with_gc_stress
|
|
63
|
+
skip 'timing-sensitive, too slow with GC.stress' if ENV['EXTRALITE_GC_STRESS']
|
|
64
|
+
end
|
|
65
|
+
end
|
data/test/test_database.rb
CHANGED
|
@@ -347,7 +347,7 @@ class DatabaseTest < Minitest::Test
|
|
|
347
347
|
|
|
348
348
|
assert_raises { @db.tables('foo') }
|
|
349
349
|
|
|
350
|
-
fn =
|
|
350
|
+
fn = tempfile_path('extralite_test_tables_with_db_name')
|
|
351
351
|
@db.execute "attach database '#{fn}' as foo"
|
|
352
352
|
|
|
353
353
|
assert_equal [], @db.tables('foo')
|
|
@@ -382,7 +382,7 @@ class DatabaseTest < Minitest::Test
|
|
|
382
382
|
end
|
|
383
383
|
|
|
384
384
|
def test_integrity_check_damaged
|
|
385
|
-
fn =
|
|
385
|
+
fn = tempfile_path('extralite_test_integrity_check_damaged')
|
|
386
386
|
db = Extralite::Database.new(fn)
|
|
387
387
|
|
|
388
388
|
db.execute <<~SQL
|
|
@@ -422,7 +422,7 @@ class DatabaseTest < Minitest::Test
|
|
|
422
422
|
end
|
|
423
423
|
|
|
424
424
|
def test_quick_check_damaged
|
|
425
|
-
fn =
|
|
425
|
+
fn = tempfile_path('extralite_test_quick_check_damaged')
|
|
426
426
|
db = Extralite::Database.new(fn)
|
|
427
427
|
|
|
428
428
|
db.execute <<~SQL
|
|
@@ -882,7 +882,8 @@ class DatabaseTest < Minitest::Test
|
|
|
882
882
|
end
|
|
883
883
|
|
|
884
884
|
def test_database_busy_timeout
|
|
885
|
-
|
|
885
|
+
skip_with_gc_stress
|
|
886
|
+
fn = tempfile_path('extralite_test_database_busy_timeout')
|
|
886
887
|
db1 = Extralite::Database.new(fn)
|
|
887
888
|
db2 = Extralite::Database.new(fn)
|
|
888
889
|
|
|
@@ -978,13 +979,13 @@ class DatabaseTest < Minitest::Test
|
|
|
978
979
|
db = Extralite::Database.new(':memory:', gvl_release_threshold: 23)
|
|
979
980
|
assert_equal 23, db.gvl_release_threshold
|
|
980
981
|
|
|
981
|
-
fn =
|
|
982
|
+
fn = tempfile_path('extralite_test_database_initialize_modern')
|
|
982
983
|
db = Extralite::Database.new(fn)
|
|
983
984
|
assert_equal 'wal', db.pragma(:journal_mode)
|
|
984
985
|
assert_equal 1, db.pragma(:synchronous)
|
|
985
986
|
assert_equal 1, db.pragma(:foreign_keys)
|
|
986
987
|
|
|
987
|
-
fn =
|
|
988
|
+
fn = tempfile_path('extralite_test_database_initialize_legacy')
|
|
988
989
|
db = Extralite::Database.new(fn, legacy: true)
|
|
989
990
|
assert_equal 'delete', db.pragma(:journal_mode)
|
|
990
991
|
assert_equal 2, db.pragma(:synchronous)
|
|
@@ -1011,7 +1012,7 @@ class DatabaseTest < Minitest::Test
|
|
|
1011
1012
|
end
|
|
1012
1013
|
|
|
1013
1014
|
def test_database_transaction_commit
|
|
1014
|
-
path =
|
|
1015
|
+
path = tempfile_path('extralite_test_database_transaction_commit')
|
|
1015
1016
|
db1 = Extralite::Database.new(path)
|
|
1016
1017
|
db2 = Extralite::Database.new(path)
|
|
1017
1018
|
|
|
@@ -1165,7 +1166,7 @@ class DatabaseTest < Minitest::Test
|
|
|
1165
1166
|
end
|
|
1166
1167
|
|
|
1167
1168
|
def test_wal_checkpoint
|
|
1168
|
-
fn =
|
|
1169
|
+
fn = tempfile_path('extralite_test_wal_checkpoint')
|
|
1169
1170
|
|
|
1170
1171
|
db = Extralite::Database.new(fn)
|
|
1171
1172
|
|
|
@@ -1258,7 +1259,7 @@ end
|
|
|
1258
1259
|
|
|
1259
1260
|
class ScenarioTest < Minitest::Test
|
|
1260
1261
|
def setup
|
|
1261
|
-
@fn =
|
|
1262
|
+
@fn = tempfile_path('extralite_scenario_test')
|
|
1262
1263
|
@db = Extralite::Database.new(@fn, legacy: true)
|
|
1263
1264
|
@db.query('create table if not exists t (x,y,z)')
|
|
1264
1265
|
@db.query('delete from t')
|
|
@@ -1411,7 +1412,7 @@ class BackupTest < Minitest::Test
|
|
|
1411
1412
|
end
|
|
1412
1413
|
|
|
1413
1414
|
def test_backup_with_fn
|
|
1414
|
-
tmp_fn =
|
|
1415
|
+
tmp_fn = tempfile_path('extralite_test_backup_with_fn')
|
|
1415
1416
|
@src.backup(tmp_fn)
|
|
1416
1417
|
|
|
1417
1418
|
db = Extralite::Database.new(tmp_fn)
|
|
@@ -1438,6 +1439,7 @@ class ConcurrencyTest < Minitest::Test
|
|
|
1438
1439
|
end
|
|
1439
1440
|
|
|
1440
1441
|
def test_gvl_always_release
|
|
1442
|
+
skip_with_gc_stress
|
|
1441
1443
|
skip if !IS_LINUX
|
|
1442
1444
|
|
|
1443
1445
|
delays = []
|
|
@@ -1522,6 +1524,7 @@ class ConcurrencyTest < Minitest::Test
|
|
|
1522
1524
|
end
|
|
1523
1525
|
|
|
1524
1526
|
def test_progress_handler_simple
|
|
1527
|
+
skip_with_gc_stress
|
|
1525
1528
|
db = Extralite::Database.new(':memory:')
|
|
1526
1529
|
|
|
1527
1530
|
# SQLite's behaviour post 3.46.0 has changed, such that the first time the
|
|
@@ -1714,6 +1717,7 @@ class ConcurrencyTest < Minitest::Test
|
|
|
1714
1717
|
SQL
|
|
1715
1718
|
|
|
1716
1719
|
def test_progress_handler_timeout_interrupt
|
|
1720
|
+
skip_with_gc_stress
|
|
1717
1721
|
db = Extralite::Database.new(':memory:')
|
|
1718
1722
|
t0 = Time.now
|
|
1719
1723
|
db.on_progress do
|
|
@@ -1756,6 +1760,7 @@ class ConcurrencyTest < Minitest::Test
|
|
|
1756
1760
|
end
|
|
1757
1761
|
|
|
1758
1762
|
def test_progress_handler_timeout_raise
|
|
1763
|
+
skip_with_gc_stress
|
|
1759
1764
|
db = Extralite::Database.new(':memory:')
|
|
1760
1765
|
t0 = Time.now
|
|
1761
1766
|
db.on_progress do
|
|
@@ -1795,7 +1800,8 @@ class ConcurrencyTest < Minitest::Test
|
|
|
1795
1800
|
end
|
|
1796
1801
|
|
|
1797
1802
|
def test_progress_handler_busy_timeout
|
|
1798
|
-
|
|
1803
|
+
skip_with_gc_stress
|
|
1804
|
+
fn = tempfile_path('extralite_test_progress_handler_busy_timeout')
|
|
1799
1805
|
db1 = Extralite::Database.new(fn)
|
|
1800
1806
|
db2 = Extralite::Database.new(fn)
|
|
1801
1807
|
|
|
@@ -1865,7 +1871,7 @@ class RactorTest < Minitest::Test
|
|
|
1865
1871
|
def test_ractor_simple
|
|
1866
1872
|
skip if SKIP_RACTOR_TESTS
|
|
1867
1873
|
|
|
1868
|
-
fn =
|
|
1874
|
+
fn = tempfile_path('extralite_test_database_in_ractor')
|
|
1869
1875
|
|
|
1870
1876
|
r = Ractor.new do
|
|
1871
1877
|
path = receive
|
|
@@ -1917,7 +1923,7 @@ class RactorTest < Minitest::Test
|
|
|
1917
1923
|
def test_ractor_stress
|
|
1918
1924
|
skip if SKIP_RACTOR_TESTS
|
|
1919
1925
|
|
|
1920
|
-
fn =
|
|
1926
|
+
fn = tempfile_path('extralite_test_ractor_stress')
|
|
1921
1927
|
random = Random.new.freeze
|
|
1922
1928
|
|
|
1923
1929
|
ractors = (0..9).map do |ractor_number|
|
|
@@ -2307,7 +2313,7 @@ class StmtCacheText < Minitest::Test
|
|
|
2307
2313
|
end
|
|
2308
2314
|
|
|
2309
2315
|
def test_stmt_cache_close
|
|
2310
|
-
fn =
|
|
2316
|
+
fn = tempfile_path('extralite_test_stmt_cache_close')
|
|
2311
2317
|
|
|
2312
2318
|
db = Extralite::Database.new(fn)
|
|
2313
2319
|
db.query('create table t (x)')
|
|
@@ -2324,7 +2330,7 @@ class StmtCacheText < Minitest::Test
|
|
|
2324
2330
|
end
|
|
2325
2331
|
|
|
2326
2332
|
def test_stmt_cache_gc
|
|
2327
|
-
fn =
|
|
2333
|
+
fn = tempfile_path('extralite_test_stmt_cache_gc')
|
|
2328
2334
|
|
|
2329
2335
|
db = Extralite::Database.new(fn)
|
|
2330
2336
|
db.query('create table t (x)')
|
|
@@ -2343,7 +2349,7 @@ class StmtCacheText < Minitest::Test
|
|
|
2343
2349
|
|
|
2344
2350
|
def test_stmt_cache_concurrent_access
|
|
2345
2351
|
# https://github.com/digital-fabric/extralite/issues/88
|
|
2346
|
-
fn =
|
|
2352
|
+
fn = tempfile_path('extralite_test_stmt_cache_concurrent_access')
|
|
2347
2353
|
|
|
2348
2354
|
a = Extralite::Database.new(fn)
|
|
2349
2355
|
a.execute("pragma journal_mode=wal")
|
|
@@ -2360,7 +2366,7 @@ class StmtCacheText < Minitest::Test
|
|
|
2360
2366
|
|
|
2361
2367
|
def test_stmt_cache_concurrent_transactions
|
|
2362
2368
|
# https://github.com/digital-fabric/extralite/issues/89
|
|
2363
|
-
fn =
|
|
2369
|
+
fn = tempfile_path('extralite_test_stmt_cache_concurrent_access')
|
|
2364
2370
|
|
|
2365
2371
|
a = Extralite::Database.new(fn)
|
|
2366
2372
|
a.execute('create table t (x)')
|
data/test/test_iterator.rb
CHANGED
|
@@ -118,7 +118,7 @@ class IteratorTest < Minitest::Test
|
|
|
118
118
|
end
|
|
119
119
|
|
|
120
120
|
def test_return_from_block_issue_26
|
|
121
|
-
fn =
|
|
121
|
+
fn = tempfile_path('extralite_test_return_from_block_issue_26')
|
|
122
122
|
db = Extralite::Database.new(fn)
|
|
123
123
|
|
|
124
124
|
λ = ->(sql) {
|
data/test/test_query.rb
CHANGED
|
@@ -933,7 +933,7 @@ class QueryTest < Minitest::Test
|
|
|
933
933
|
def test_query_after_db_close
|
|
934
934
|
assert_equal [{ x: 4, y: 5, z: 6}], @query.bind(4).to_a
|
|
935
935
|
@db.close
|
|
936
|
-
|
|
936
|
+
assert_raises(Extralite::Error) { @query.bind(4).to_a }
|
|
937
937
|
end
|
|
938
938
|
|
|
939
939
|
def test_query_eof
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: extralite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.1.
|
|
4
|
+
version: 3.1.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sharon Rosner
|
|
@@ -88,6 +88,7 @@ extra_rdoc_files:
|
|
|
88
88
|
files:
|
|
89
89
|
- ".editorconfig"
|
|
90
90
|
- ".github/FUNDING.yml"
|
|
91
|
+
- ".github/workflows/test-asan.yml"
|
|
91
92
|
- ".github/workflows/test-bundle.yml"
|
|
92
93
|
- ".github/workflows/test.yml"
|
|
93
94
|
- ".gitignore"
|
|
@@ -174,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
174
175
|
- !ruby/object:Gem::Version
|
|
175
176
|
version: '0'
|
|
176
177
|
requirements: []
|
|
177
|
-
rubygems_version: 4.0.
|
|
178
|
+
rubygems_version: 4.0.20
|
|
178
179
|
specification_version: 4
|
|
179
180
|
summary: Extra-lightweight SQLite3 wrapper for Ruby
|
|
180
181
|
test_files: []
|