From 67fc6e67dba49d6f58bfb14064b4e86a0e9c7aaf Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 27 Apr 2023 14:44:13 -0400 Subject: [PATCH 1/3] dev: use ruby_memcheck for a valgrind test task --- Gemfile | 8 ++++++++ rakelib/test.rake | 17 +++++++++++++++-- sqlite3.gemspec | 6 ------ 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index b4e2a20b..5c815c43 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,11 @@ source "https://rubygems.org" gemspec + +gem("minitest", "~> 5.15") +gem("rake-compiler", "~> 1.2.0") +gem("rake-compiler-dock", "1.3.0") +gem("rdoc", ">= 4.0", "< 7") +gem("psych", "~> 4.0") # psych 5 doesn't build on some CI platforms yet + +gem("ruby_memcheck") if Gem::Platform.local.os == "linux" diff --git a/rakelib/test.rake b/rakelib/test.rake index 73a05ddb..57ab776e 100644 --- a/rakelib/test.rake +++ b/rakelib/test.rake @@ -1,7 +1,20 @@ require "rake/testtask" - -Rake::TestTask.new(:test) do |t| +test_config = lambda do |t| t.libs << "test" t.libs << "lib" t.test_files = FileList["test/**/test_*.rb"] end + +Rake::TestTask.new(:test, &test_config) + +begin + require "ruby_memcheck" + + RubyMemcheck.config(binary_name: "sqlite3_native") + + namespace :test do + RubyMemcheck::TestTask.new(:valgrind, &test_config) + end +rescue LoadError => e + warn("NOTE: ruby_memcheck is not available in this environment: #{e}") +end diff --git a/sqlite3.gemspec b/sqlite3.gemspec index 5f9be35f..5c40d946 100644 --- a/sqlite3.gemspec +++ b/sqlite3.gemspec @@ -108,11 +108,5 @@ Gem::Specification.new do |s| s.add_dependency("mini_portile2", "~> 2.8.0") - s.add_development_dependency("minitest", "~> 5.15") - s.add_development_dependency("rake-compiler", "~> 1.2.0") - s.add_development_dependency("rake-compiler-dock", "1.3.0") - s.add_development_dependency("rdoc", ">= 4.0", "< 7") - s.add_development_dependency("psych", "~> 4.0") # psych 5 doesn't build on some CI platforms yet - s.extensions << "ext/sqlite3/extconf.rb" end From b6bc75c9126757849a857c28b7d2a8a0bbfd0d12 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 27 Apr 2023 15:36:50 -0400 Subject: [PATCH 2/3] test: add valgrind job to CI --- .github/workflows/sqlite3-ruby.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/sqlite3-ruby.yml b/.github/workflows/sqlite3-ruby.yml index 55ed0ef5..2bae48aa 100644 --- a/.github/workflows/sqlite3-ruby.yml +++ b/.github/workflows/sqlite3-ruby.yml @@ -119,3 +119,16 @@ jobs: vcpkg: sqlcipher - run: bundle exec rake compile -- --with-sqlcipher - run: bundle exec rake test + + valgrind: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: ruby/setup-ruby-pkgs@v1 + with: + ruby-version: "3.2" + bundler-cache: true + apt-get: libsqlite3-dev valgrind + - run: bundle install + - run: bundle exec rake compile + - run: bundle exec rake test:valgrind From a9bddc71d9a6ca03e389082042ff2a94fa8739a3 Mon Sep 17 00:00:00 2001 From: Mike Dalessio Date: Thu, 27 Apr 2023 15:37:17 -0400 Subject: [PATCH 3/3] test: update tests to clean up resources lots of Database, Statement, and ResultSet objects needed a `close` --- test/test_database.rb | 34 +++++++++++++++++++++++++++++----- test/test_deprecated.rb | 15 ++++++++++----- test/test_encoding.rb | 10 ++++++++++ test/test_result_set.rb | 26 ++++++++++++++++++-------- test/test_statement.rb | 25 +++++++++++++++++++++++++ test/test_statement_execute.rb | 4 ++++ 6 files changed, 96 insertions(+), 18 deletions(-) diff --git a/test/test_database.rb b/test/test_database.rb index 2ac409ff..790710d3 100644 --- a/test/test_database.rb +++ b/test/test_database.rb @@ -11,6 +11,10 @@ def setup super end + def teardown + @db.close unless @db.closed? + end + def test_segv assert_raises { SQLite3::Database.new 1 } end @@ -54,6 +58,7 @@ def test_filename_to_path assert_equal pn.realdirpath.to_s, File.realdirpath(db.filename) ensure tf.close! if tf + db.close if db end @@ -189,6 +194,8 @@ def test_execute_batch2 def test_new db = SQLite3::Database.new(':memory:') assert db + ensure + db.close if db end def test_new_yields_self @@ -210,6 +217,8 @@ def test_new_with_options :utf16 => true) end assert db + ensure + db.close if db end def test_close @@ -243,6 +252,8 @@ def test_prepare db = SQLite3::Database.new(':memory:') stmt = db.prepare('select "hello world"') assert_instance_of(SQLite3::Statement, stmt) + ensure + stmt.close if stmt end def test_block_prepare_does_not_double_close @@ -459,15 +470,19 @@ def step a end def test_authorizer_ok + statements = [] + @db.authorizer = Class.new { def call action, a, b, c, d; true end }.new - @db.prepare("select 'fooooo'") + statements << @db.prepare("select 'fooooo'") @db.authorizer = Class.new { def call action, a, b, c, d; 0 end }.new - @db.prepare("select 'fooooo'") + statements << @db.prepare("select 'fooooo'") + ensure + statements.each(&:close) end def test_authorizer_ignore @@ -476,6 +491,8 @@ def call action, a, b, c, d; nil end }.new stmt = @db.prepare("select 'fooooo'") assert_nil stmt.step + ensure + stmt.close if stmt end def test_authorizer_fail @@ -496,14 +513,18 @@ def call action, a, b, c, d; false end end @db.authorizer = nil - @db.prepare("select 'fooooo'") + s = @db.prepare("select 'fooooo'") + ensure + s.close if s end def test_close_with_open_statements - @db.prepare("select 'foo'") + s = @db.prepare("select 'foo'") assert_raises(SQLite3::BusyException) do @db.close end + ensure + s.close if s end def test_execute_with_empty_bind_params @@ -511,7 +532,10 @@ def test_execute_with_empty_bind_params end def test_query_with_named_bind_params - assert_equal [['foo']], @db.query("select :n", {'n' => 'foo'}).to_a + resultset = @db.query("select :n", {'n' => 'foo'}) + assert_equal [['foo']], resultset.to_a + ensure + resultset.close if resultset end def test_execute_with_named_bind_params diff --git a/test/test_deprecated.rb b/test/test_deprecated.rb index 4fa1dc40..1248cb9a 100644 --- a/test/test_deprecated.rb +++ b/test/test_deprecated.rb @@ -2,8 +2,6 @@ module SQLite3 class TestDeprecated < SQLite3::TestCase - attr_reader :db - def setup super @warn_before = $-w @@ -15,10 +13,13 @@ def setup def teardown super $-w = @warn_before + @db.close end def test_query_with_many_bind_params_not_nil - assert_equal [[1, 2]], db.query('select ?, ?', 1, 2).to_a + rs = @db.query('select ?, ?', 1, 2) + assert_equal [[1, 2]], rs.to_a + rs.close end def test_execute_with_many_bind_params_not_nil @@ -26,11 +27,15 @@ def test_execute_with_many_bind_params_not_nil end def test_query_with_many_bind_params - assert_equal [[nil, 1]], @db.query("select ?, ?", nil, 1).to_a + rs = @db.query("select ?, ?", nil, 1) + assert_equal [[nil, 1]], rs.to_a + rs.close end def test_query_with_nil_bind_params - assert_equal [['foo']], @db.query("select 'foo'", nil).to_a + rs = @db.query("select 'foo'", nil) + assert_equal [['foo']], rs.to_a + rs.close end def test_execute_with_many_bind_params diff --git a/test/test_encoding.rb b/test/test_encoding.rb index 32bf6169..ea1bfb47 100644 --- a/test/test_encoding.rb +++ b/test/test_encoding.rb @@ -11,6 +11,10 @@ def setup @db.execute(@create); end + def teardown + @db.close + end + def test_select_encoding_on_utf_16 str = "foo" utf16 = ([1].pack("I") == [1].pack("N")) ? "UTF-16BE" : "UTF-16LE" @@ -24,6 +28,7 @@ def test_select_encoding_on_utf_16 assert_equal 1, stmt.to_a.length stmt.reset! end + stmt.close end def test_insert_encoding @@ -39,6 +44,7 @@ def test_insert_encoding stmt.to_a stmt.reset! end + stmt.close db.execute('select data from ex').flatten.each do |s| assert_equal str, s @@ -55,6 +61,7 @@ def test_default_internal_is_honored stmt = @db.prepare('insert into ex(data) values (?)') stmt.bind_param 1, str stmt.step + stmt.close Encoding.default_internal = 'EUC-JP' string = @db.execute('select data from ex').first.first @@ -73,6 +80,7 @@ def test_blob_is_binary stmt = @db.prepare('insert into foo(data) values (?)') stmt.bind_param(1, SQLite3::Blob.new(str)) stmt.step + stmt.close string = @db.execute('select data from foo').first.first assert_equal Encoding.find('ASCII-8BIT'), string.encoding @@ -85,6 +93,7 @@ def test_blob_is_ascii8bit stmt = @db.prepare('insert into foo(data) values (?)') stmt.bind_param(1, str.dup.force_encoding("ASCII-8BIT")) stmt.step + stmt.close string = @db.execute('select data from foo').first.first assert_equal Encoding.find('ASCII-8BIT'), string.encoding @@ -97,6 +106,7 @@ def test_blob_with_eucjp stmt = @db.prepare('insert into foo(data) values (?)') stmt.bind_param(1, SQLite3::Blob.new(str)) stmt.step + stmt.close string = @db.execute('select data from foo').first.first assert_equal Encoding.find('ASCII-8BIT'), string.encoding diff --git a/test/test_result_set.rb b/test/test_result_set.rb index fa3df516..f16ffdf6 100644 --- a/test/test_result_set.rb +++ b/test/test_result_set.rb @@ -2,29 +2,38 @@ module SQLite3 class TestResultSet < SQLite3::TestCase + def setup + @db = SQLite3::Database.new ':memory:' + super + end + + def teardown + super + @db.close + end + def test_each_hash - db = SQLite3::Database.new ':memory:' - db.execute "create table foo ( a integer primary key, b text )" + @db.execute "create table foo ( a integer primary key, b text )" list = ('a'..'z').to_a list.each do |t| - db.execute "insert into foo (b) values (\"#{t}\")" + @db.execute "insert into foo (b) values (\"#{t}\")" end - rs = db.prepare('select * from foo').execute + rs = @db.prepare('select * from foo').execute rs.each_hash do |hash| assert_equal list[hash['a'] - 1], hash['b'] end + rs.close end def test_next_hash - db = SQLite3::Database.new ':memory:' - db.execute "create table foo ( a integer primary key, b text )" + @db.execute "create table foo ( a integer primary key, b text )" list = ('a'..'z').to_a list.each do |t| - db.execute "insert into foo (b) values (\"#{t}\")" + @db.execute "insert into foo (b) values (\"#{t}\")" end - rs = db.prepare('select * from foo').execute + rs = @db.prepare('select * from foo').execute rows = [] while row = rs.next_hash rows << row @@ -32,6 +41,7 @@ def test_next_hash rows.each do |hash| assert_equal list[hash['a'] - 1], hash['b'] end + rs.close end end end diff --git a/test/test_statement.rb b/test/test_statement.rb index 734d1bdd..4881f180 100644 --- a/test/test_statement.rb +++ b/test/test_statement.rb @@ -7,6 +7,11 @@ def setup @stmt = SQLite3::Statement.new(@db, "select 'foo'") end + def teardown + @stmt.close if !@stmt.closed? + @db.close + end + def test_double_close_does_not_segv @db.execute 'CREATE TABLE "things" ("number" float NOT NULL)' @@ -35,6 +40,8 @@ def test_insert_duplicate_records # Older versions of SQLite return: # column *column_name* is not unique assert_match(/(column(s)? .* (is|are) not unique|UNIQUE constraint failed: .*)/, exception.message) + + stmt.close end ### @@ -46,6 +53,7 @@ def test_database_name if stmt.respond_to?(:database_name) assert_equal 'main', stmt.database_name(0) end + stmt.close end def test_prepare_blob @@ -77,6 +85,7 @@ def test_new_closed_handle def test_new_with_remainder stmt = SQLite3::Statement.new(@db, "select 'foo';bar") assert_equal 'bar', stmt.remainder + stmt.close end def test_empty_remainder @@ -101,6 +110,7 @@ def test_bind_param_string result = nil stmt.each { |x| result = x } assert_equal ['hello'], result + stmt.close end def test_bind_param_int @@ -109,6 +119,7 @@ def test_bind_param_int result = nil stmt.each { |x| result = x } assert_equal [10], result + stmt.close end def test_bind_nil @@ -117,6 +128,7 @@ def test_bind_nil result = nil stmt.each { |x| result = x } assert_equal [nil], result + stmt.close end def test_bind_blob @@ -125,6 +137,7 @@ def test_bind_blob stmt.bind_param(1, SQLite3::Blob.new('hello')) stmt.execute row = @db.execute('select * from foo') + stmt.close assert_equal ['hello'], row.first assert_equal ['blob'], row.first.types @@ -136,6 +149,7 @@ def test_bind_64 result = nil stmt.each { |x| result = x } assert_equal [2 ** 31], result + stmt.close end def test_bind_double @@ -144,6 +158,7 @@ def test_bind_double result = nil stmt.each { |x| result = x } assert_equal [2.2], result + stmt.close end def test_named_bind @@ -152,6 +167,7 @@ def test_named_bind result = nil stmt.each { |x| result = x } assert_equal ['hello'], result + stmt.close end def test_named_bind_no_colon @@ -160,6 +176,7 @@ def test_named_bind_no_colon result = nil stmt.each { |x| result = x } assert_equal ['hello'], result + stmt.close end def test_named_bind_symbol @@ -168,6 +185,7 @@ def test_named_bind_symbol result = nil stmt.each { |x| result = x } assert_equal ['hello'], result + stmt.close end def test_named_bind_not_found @@ -175,6 +193,7 @@ def test_named_bind_not_found assert_raises(SQLite3::Exception) do stmt.bind_param('bar', 'hello') end + stmt.close end def test_each @@ -225,16 +244,19 @@ def test_column_name def test_bind_parameter_count stmt = SQLite3::Statement.new(@db, "select ?, ?, ?") assert_equal 3, stmt.bind_parameter_count + stmt.close end def test_execute_with_varargs stmt = @db.prepare('select ?, ?') assert_equal [[nil, nil]], stmt.execute(nil, nil).to_a + stmt.close end def test_execute_with_hash stmt = @db.prepare('select :n, :h') assert_equal [[10, nil]], stmt.execute('n' => 10, 'h' => nil).to_a + stmt.close end def test_with_error @@ -244,6 +266,7 @@ def test_with_error stmt.execute('employee-1') rescue SQLite3::ConstraintException stmt.reset! assert stmt.execute('employee-2') + stmt.close end def test_clear_bindings! @@ -258,6 +281,8 @@ def test_clear_bindings! while x = stmt.step assert_equal [nil, nil], x end + + stmt.close end end end diff --git a/test/test_statement_execute.rb b/test/test_statement_execute.rb index 63e022b2..6ff0a527 100644 --- a/test/test_statement_execute.rb +++ b/test/test_statement_execute.rb @@ -8,6 +8,10 @@ def setup "CREATE TABLE items (id integer PRIMARY KEY, number integer)") end + def teardown + @db.close + end + def test_execute_insert ps = @db.prepare("INSERT INTO items (number) VALUES (:n)") ps.execute('n'=>10)