From 2053f173773b8f7502ba472044554cdb29554b9a Mon Sep 17 00:00:00 2001 From: richardjcai Date: Wed, 14 Apr 2021 14:48:21 -0400 Subject: [PATCH] roachtest: add roachtest against pg ruby driver. Release note: None --- pkg/cmd/roachtest/BUILD.bazel | 2 + pkg/cmd/roachtest/registry.go | 1 + pkg/cmd/roachtest/ruby_pg.go | 205 ++++++++++++++ pkg/cmd/roachtest/ruby_pg_blocklist.go | 194 +++++++++++++ pkg/cmd/roachtest/ruby_pg_helpers.rb | 377 +++++++++++++++++++++++++ pkg/testutils/lint/lint_test.go | 1 + 6 files changed, 780 insertions(+) create mode 100644 pkg/cmd/roachtest/ruby_pg.go create mode 100644 pkg/cmd/roachtest/ruby_pg_blocklist.go create mode 100644 pkg/cmd/roachtest/ruby_pg_helpers.rb diff --git a/pkg/cmd/roachtest/BUILD.bazel b/pkg/cmd/roachtest/BUILD.bazel index c8d14c7ff1f5..66bcad5d8628 100644 --- a/pkg/cmd/roachtest/BUILD.bazel +++ b/pkg/cmd/roachtest/BUILD.bazel @@ -93,6 +93,8 @@ go_library( "restart.go", "restore.go", "roachmart.go", + "ruby_pg.go", + "ruby_pg_blocklist.go", "schema_change_database_version_upgrade.go", "schemachange.go", "schemachange_random_load.go", diff --git a/pkg/cmd/roachtest/registry.go b/pkg/cmd/roachtest/registry.go index 35a09cc773db..671dfaafecfc 100644 --- a/pkg/cmd/roachtest/registry.go +++ b/pkg/cmd/roachtest/registry.go @@ -83,6 +83,7 @@ func registerTests(r *testRegistry) { registerRestoreNodeShutdown(r) registerRestore(r) registerRoachmart(r) + registerRubyPG(r) registerSchemaChangeBulkIngest(r) registerSchemaChangeDatabaseVersionUpgrade(r) registerSchemaChangeDuringKV(r) diff --git a/pkg/cmd/roachtest/ruby_pg.go b/pkg/cmd/roachtest/ruby_pg.go new file mode 100644 index 000000000000..a2881368d11a --- /dev/null +++ b/pkg/cmd/roachtest/ruby_pg.go @@ -0,0 +1,205 @@ +// Copyright 2021 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package main + +import ( + "bufio" + "bytes" + "context" + "fmt" + "regexp" + + "github.com/cockroachdb/cockroach/pkg/util/log" + "github.com/stretchr/testify/require" +) + +var rubyPGTestFailureRegex = regexp.MustCompile(`^rspec ./.*# .*`) +var rubyPGVersion = "v1.2.3" + +// This test runs Ruby PG's full test suite against a single cockroach node. +func registerRubyPG(r *testRegistry) { + runRubyPGTest := func( + ctx context.Context, + t *test, + c *cluster, + ) { + if c.isLocal() { + t.Fatal("cannot be run in local mode") + } + node := c.Node(1) + t.Status("setting up cockroach") + c.Put(ctx, cockroach, "./cockroach", c.All()) + if err := c.PutLibraries(ctx, "./lib"); err != nil { + t.Fatal(err) + } + c.Start(ctx, t, c.All()) + + version, err := fetchCockroachVersion(ctx, c, node[0], nil) + if err != nil { + t.Fatal(err) + } + + if err := alterZoneConfigAndClusterSettings(ctx, version, c, node[0], nil); err != nil { + t.Fatal(err) + } + + t.Status("cloning rails and installing prerequisites") + + c.l.Printf("Supported ruby-pg version is %s.", rubyPGVersion) + + if err := repeatRunE( + ctx, c, node, "update apt-get", `sudo apt-get -qq update`, + ); err != nil { + t.Fatal(err) + } + + if err := repeatRunE( + ctx, + c, + node, + "install dependencies", + `sudo apt-get -qq install ruby-full ruby-dev rubygems build-essential zlib1g-dev libpq-dev libsqlite3-dev`, + ); err != nil { + t.Fatal(err) + } + + if err := repeatRunE( + ctx, + c, + node, + "install ruby 2.7", + `mkdir -p ruby-install && \ + curl -fsSL https://github.com/postmodern/ruby-install/archive/v0.6.1.tar.gz | tar --strip-components=1 -C ruby-install -xz && \ + sudo make -C ruby-install install && \ + sudo ruby-install --system ruby 2.7.1 && \ + sudo gem update --system`, + ); err != nil { + t.Fatal(err) + } + + if err := repeatRunE( + ctx, c, node, "remove old ruby-pg", `sudo rm -rf /mnt/data1/ruby-pg`, + ); err != nil { + t.Fatal(err) + } + + if err := repeatGitCloneE( + ctx, + t.l, + c, + "https://github.com/ged/ruby-pg.git", + "/mnt/data1/ruby-pg", + rubyPGVersion, + node, + ); err != nil { + t.Fatal(err) + } + + t.Status("installing bundler") + if err := repeatRunE( + ctx, + c, + node, + "installing bundler", + `cd /mnt/data1/ruby-pg/ && sudo gem install bundler:2.1.4`, + ); err != nil { + t.Fatal(err) + } + + t.Status("installing gems") + if err := repeatRunE( + ctx, + c, + node, + "installing gems", + `cd /mnt/data1/ruby-pg/ && sudo bundle install`, + ); err != nil { + t.Fatal(err) + } + + if err := repeatRunE( + ctx, c, node, "remove old ruby-pg helpers.rb", `sudo rm /mnt/data1/ruby-pg/spec/helpers.rb`, + ); err != nil { + t.Fatal(err) + } + + // Write the cockroach config into the test suite to use. + err = c.PutE(ctx, c.l, "./pkg/cmd/roachtest/ruby_pg_helpers.rb", "/mnt/data1/ruby-pg/spec/helpers.rb", c.All()) + require.NoError(t, err) + + t.Status("running ruby-pg test suite") + // Note that this is expected to return an error, since the test suite + // will fail. And it is safe to swallow it here. + rawResults, _ := c.RunWithBuffer(ctx, t.l, node, + `cd /mnt/data1/ruby-pg/ && sudo rake`, + ) + + c.l.Printf("Test Results:\n%s", rawResults) + + // Find all the failed and errored tests. + results := newORMTestsResults() + blocklistName, expectedFailures, _, _ := rubyPGBlocklist.getLists(version) + if expectedFailures == nil { + t.Fatalf("No ruby-pg blocklist defined for cockroach version %s", version) + } + + scanner := bufio.NewScanner(bytes.NewReader(rawResults)) + for scanner.Scan() { + match := rubyPGTestFailureRegex.FindStringSubmatch(scanner.Text()) + if match == nil { + continue + } + if len(match) != 1 { + log.Fatalf(ctx, "expected one match for test name, found: %d", len(match)) + } + + // Take the first test name. + test := match[0] + + // This regex is used to get the name of the test. + // The test name follows the file name and a hashtag. + // ie. test.rb:99 # TEST NAME. + strs := regexp.MustCompile("^rspec .*.rb.*([0-9]|]) # ").Split(test, -1) + if len(strs) != 2 { + log.Fatalf(ctx, "expected test output line to be split into two strings") + } + test = strs[1] + + issue, expectedFailure := expectedFailures[test] + switch { + case expectedFailure: + results.results[test] = fmt.Sprintf("--- FAIL: %s - %s (expected)", + test, maybeAddGithubLink(issue), + ) + results.failExpectedCount++ + results.currentFailures = append(results.currentFailures, test) + case !expectedFailure: + results.results[test] = fmt.Sprintf("--- PASS: %s - %s (unexpected)", + test, maybeAddGithubLink(issue), + ) + } + results.runTests[test] = struct{}{} + } + + results.summarizeAll(t, "ruby-pg", blocklistName, expectedFailures, version, rubyPGVersion) + } + + r.Add(testSpec{ + MinVersion: "v20.1.0", + Name: "ruby-pg", + Owner: OwnerSQLExperience, + Cluster: makeClusterSpec(1), + Tags: []string{`default`, `orm`}, + Run: func(ctx context.Context, t *test, c *cluster) { + runRubyPGTest(ctx, t, c) + }, + }) +} diff --git a/pkg/cmd/roachtest/ruby_pg_blocklist.go b/pkg/cmd/roachtest/ruby_pg_blocklist.go new file mode 100644 index 000000000000..7b1d72895017 --- /dev/null +++ b/pkg/cmd/roachtest/ruby_pg_blocklist.go @@ -0,0 +1,194 @@ +// Copyright 2021 The Cockroach Authors. +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package main + +var rubyPGBlocklist = blocklistsForVersion{ + {"v20.2", "rubyPGBlockList20_2", rubyPGBlockList20_2, "rubyPGIgnoreList20_2", rubyPGIgnoreList20_2}, + {"v21.1", "rubyPGBlockList21_1", rubyPGBlockList21_1, "rubyPGIgnoreList21_1", rubyPGIgnoreList21_1}, +} + +var rubyPGBlockList21_1 = rubyPGBlockList20_2 + +var rubyPGBlockList20_2 = blocklist{ + "PG::TypeMapByColumn will deny copy queries with different column count": "unknown", + "PG::TypeMapByColumn should gracefully handle not initialized state": "unknown", + "PG::TypeMapByColumn forwards get_copy_data conversions to another TypeMapByColumn as #default_type_map": "unknown", + "PG::TypeMapByOid should build a TypeMapByColumn when assigned and the number of rows is high enough": "unknown", + "PG::TypeMapByOid should allow mixed type conversions in binary format": "unknown", + "PG::TypeMapByOid should allow mixed type conversions in text format": "unknown", + "PG::TypeMapByOid should allow building new TypeMapByColumn for a given result": "unknown", + "running with sync_* methods PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts three arguments": "unknown", + "running with sync_* methods PG::Connection calls a block for NOTIFY events if one is given": "unknown", + "running with sync_* methods PG::Connection sends nil as the payload if the notification wasn't given one": "unknown", + "running with sync_* methods PG::Connection described_class#block shouldn't block a second thread": "unknown", + "running with sync_* methods PG::Connection gracefully handle SQL statements while in #copy_data for output": "unknown", + "running with sync_* methods PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts two arguments": "unknown", + "running with sync_* methods PG::Connection not read past the end of a large object": "unknown", + "running with sync_* methods PG::Connection automatically rolls back a transaction started with Connection#transaction if an exception is raised": "unknown", + "running with sync_* methods PG::Connection doesn't leave stale server connections after finish": "unknown", + "running with sync_* methods PG::Connection can handle server errors in #copy_data for input": "unknown", + "running with sync_* methods PG::Connection can process #copy_data output queries": "unknown", + "running with sync_* methods PG::Connection gracefully handle SQL statements while in #copy_data for input": "unknown", + "running with sync_* methods PG::Connection doesn't collapse sequential notifications": "unknown", + "running with sync_* methods PG::Connection can process #copy_data input queries": "unknown", + "running with sync_* methods PG::Connection can handle client errors in #copy_data for output": "unknown", + "running with sync_* methods PG::Connection correctly finishes COPY queries passed to #async_exec": "unknown", + "running with sync_* methods PG::Connection calls the block supplied to wait_for_notify with the notify payload if it doesn't accept arguments": "unknown", + "running with sync_* methods PG::Connection can handle client errors in #copy_data for input": "unknown", + "running with sync_* methods PG::Connection can handle server errors in #copy_data for output": "unknown", + "running with sync_* methods PG::Connection returns notifications which are already in the queue before wait_for_notify is called without waiting for the socket to become readable": "unknown", + "running with sync_* methods PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts any number of arguments": "unknown", + "running with sync_* methods PG::Connection allows a query to be cancelled": "unknown", + "running with sync_* methods PG::Connection accepts nil as the timeout in #wait_for_notify ": "unknown", + "running with sync_* methods PG::Connection can handle incomplete #copy_data output queries": "unknown", + "running with sync_* methods PG::Connection can receive notices while waiting for NOTIFY without exceeding the timeout": "unknown", + "running with sync_* methods PG::Connection trace and untrace client-server communication": "unknown", + "running with sync_* methods PG::Connection returns the block result from Connection#transaction": "unknown", + "running with sync_* methods PG::Connection can wait for NOTIFY events": "unknown", + "running with sync_* methods PG::Connection set_single_row_mode should receive rows before entire query is finished": "unknown", + "running with sync_* methods PG::Connection set_single_row_mode should receive rows before entire query fails": "unknown", + "running with sync_* methods PG::Connection deprecated forms of methods should forward send_query to send_query_params": "unknown", + "running with sync_* methods PG::Connection deprecated forms of methods should forward exec to exec_params": "unknown", + "running with sync_* methods PG::Connection multinationalization support returns properly encoded text from notifies": "unknown", + "running with sync_* methods PG::Connection multinationalization support receives properly encoded text from wait_for_notify": "unknown", + "running with sync_* methods PG::Connection multinationalization support receives properly encoded messages in the notice callbacks": "unknown", + "running with sync_* methods PG::Connection multinationalization support encodes exception messages with the connection's encoding (#96)": "unknown", + "running with sync_* methods PG::Connection multinationalization support handles clearing result in or after set_notice_receiver": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings to #send_describe_portal": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings and parameters to #prepare and #exec_prepared": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert query string and parameters to #send_query_params": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings and parameters to #send_prepare and #send_query_prepared": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings to #describe_portal": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert query string and parameters to #exec_params": "unknown", + "running with sync_* methods PG::Connection multinationalization support respect and convert character encoding of input strings should convert error string to #put_copy_end": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped literal": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support should return results in the same encoding as the client (EUC-JP)": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support the connection should use JOHAB dummy encoding when it's set to JOHAB": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support should return results in the same encoding as the client (iso-8859-1)": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support can use an encoding with high index for client encoding": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped string": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the previous string encoding for escaped string": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the previous string encoding for quote_ident": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for quote_ident": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support the connection should return ASCII-8BIT when it's set to SQL_ASCII": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped identifier": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support returns the results in the correct encoding even if the client_encoding has changed since the results were fetched": "unknown", + "running with sync_* methods PG::Connection multinationalization support rubyforge #22925: m17n support raises appropriate error if set_client_encoding is called with invalid arguments": "unknown", + "running with sync_* methods PG::Connection multinationalization support Ruby 1.9.x default_internal encoding allows users of the async interface to set the client_encoding to the default_internal": "unknown", + "running with sync_* methods PG::Connection multinationalization support Ruby 1.9.x default_internal encoding honors the Encoding.default_internal if it's set and the synchronous interface is used": "unknown", + "running with sync_* methods PG::Connection type casting shouldn't type map params unless requested": "unknown", + "running with sync_* methods PG::Connection type casting can type cast parameters to put_copy_data with explicit encoder": "unknown", + "running with sync_* methods PG::Connection type casting with default query type map can process #copy_data input queries with row encoder and respects character encoding": "unknown", + "running with sync_* methods PG::Connection type casting with default result type map should work with arbitrary number of params in conjunction with type casting": "unknown", + "running with sync_* methods PG::Connection type casting with default result type map can process #copy_data output with row decoder and respects character encoding": "unknown", + "running with sync_* methods PG::Connection type casting with default result type map should respect a type mapping for result": "unknown", + "running with sync_* methods PG::Connection type casting with default result type map can type cast #copy_data output with explicit decoder": "unknown", + "PG::Result raises a proper exception for a nonexistant schema": "unknown", + "PG::Result encapsulates errors in a PG::Error object": "unknown", + "PG::Result encapsulates database object names for integrity constraint violations": "unknown", + "Basic type mapping PG::BasicTypeMapForResults with usage of result oids for copy decoder selection can type cast #copy_data output with explicit decoder": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 0 timestamps per TimestampLocal": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 1 timestamps per TimestampUtc": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should do float type conversions": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should do cidr type conversions": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should do text datetime without time zone type conversions": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 0 timestamps per TimestampUtc": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 1 timestamps per TimestampUtcToLocal": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 0 timestamps per TimestampUtcToLocal": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should do array type conversions": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 1 timestamps with time zone": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 0 timestamps with time zone": "unknown", + "Basic type mapping PG::BasicTypeMapForResults connection wide type mapping should convert format 1 timestamps per TimestampLocal": "unknown", + "Basic type mapping PG::BasicTypeMapBasedOnResult with usage of result oids for copy encoder selection can type cast #copy_data input with explicit encoder": "unknown", + "Basic type mapping PG::BasicTypeMapBasedOnResult with usage of result oids for bind params encoder selection can do JSON conversions": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do IPAddr param encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do bigdecimal param encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do hash-as-json encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do array of string encoding on unknown classes": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do default array-as-array param encoding with Time objects": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do default array-as-array param encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do basic param encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do basic Time encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do basic param encoding of various float values": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries should do array-as-json encoding": "unknown", + "Basic type mapping PG::BasicTypeMapForQueries Record encoding should do array-as-record encoding": "unknown", + "PG::TypeMapByClass should expire the cache after changes to the coders": "unknown", + "PG::Connection doesn't leave stale server connections after finish": "unknown", + "PG::Connection sends nil as the payload if the notification wasn't given one": "unknown", + "PG::Connection can handle client errors in #copy_data for input": "unknown", + "PG::Connection automatically rolls back a transaction started with Connection#transaction if an exception is raised": "unknown", + "PG::Connection returns the block result from Connection#transaction": "unknown", + "PG::Connection can handle server errors in #copy_data for output": "unknown", + "PG::Connection calls the block supplied to wait_for_notify with the notify payload if it doesn't accept arguments": "unknown", + "PG::Connection allows a query to be cancelled": "unknown", + "PG::Connection can process #copy_data output queries": "unknown", + "PG::Connection can handle client errors in #copy_data for output": "unknown", + "PG::Connection returns notifications which are already in the queue before wait_for_notify is called without waiting for the socket to become readable": "unknown", + "PG::Connection can process #copy_data input queries": "unknown", + "PG::Connection not read past the end of a large object": "unknown", + "PG::Connection doesn't collapse sequential notifications": "unknown", + "PG::Connection gracefully handle SQL statements while in #copy_data for output": "unknown", + "PG::Connection calls a block for NOTIFY events if one is given": "unknown", + "PG::Connection can wait for NOTIFY events": "unknown", + "PG::Connection can handle incomplete #copy_data output queries": "unknown", + "PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts any number of arguments": "unknown", + "PG::Connection gracefully handle SQL statements while in #copy_data for input": "unknown", + "PG::Connection can handle server errors in #copy_data for input": "unknown", + "PG::Connection can receive notices while waiting for NOTIFY without exceeding the timeout": "unknown", + "PG::Connection correctly finishes COPY queries passed to #async_exec": "unknown", + "PG::Connection trace and untrace client-server communication": "unknown", + "PG::Connection accepts nil as the timeout in #wait_for_notify ": "unknown", + "PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts three arguments": "unknown", + "PG::Connection described_class#block shouldn't block a second thread": "unknown", + "PG::Connection calls the block supplied to wait_for_notify with the notify payload if it accepts two arguments": "unknown", + "PG::Connection multinationalization support handles clearing result in or after set_notice_receiver": "unknown", + "PG::Connection multinationalization support receives properly encoded messages in the notice callbacks": "unknown", + "PG::Connection multinationalization support encodes exception messages with the connection's encoding (#96)": "unknown", + "PG::Connection multinationalization support returns properly encoded text from notifies": "unknown", + "PG::Connection multinationalization support receives properly encoded text from wait_for_notify": "unknown", + "PG::Connection multinationalization support Ruby 1.9.x default_internal encoding allows users of the async interface to set the client_encoding to the default_internal": "unknown", + "PG::Connection multinationalization support Ruby 1.9.x default_internal encoding honors the Encoding.default_internal if it's set and the synchronous interface is used": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the previous string encoding for quote_ident": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support raises appropriate error if set_client_encoding is called with invalid arguments": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support can use an encoding with high index for client encoding": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped string": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped literal": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support returns the results in the correct encoding even if the client_encoding has changed since the results were fetched": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the previous string encoding for escaped string": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support the connection should use JOHAB dummy encoding when it's set to JOHAB": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for escaped identifier": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support uses the client encoding for quote_ident": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support should return results in the same encoding as the client (iso-8859-1)": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support the connection should return ASCII-8BIT when it's set to SQL_ASCII": "unknown", + "PG::Connection multinationalization support rubyforge #22925: m17n support should return results in the same encoding as the client (EUC-JP)": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert query string and parameters to #send_query_params": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings and parameters to #send_prepare and #send_query_prepared": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert query string and parameters to #exec_params": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings to #describe_portal": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings and parameters to #prepare and #exec_prepared": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert strings to #send_describe_portal": "unknown", + "PG::Connection multinationalization support respect and convert character encoding of input strings should convert error string to #put_copy_end": "unknown", + "PG::Connection type casting can type cast parameters to put_copy_data with explicit encoder": "unknown", + "PG::Connection type casting shouldn't type map params unless requested": "unknown", + "PG::Connection type casting with default query type map can process #copy_data input queries with row encoder and respects character encoding": "unknown", + "PG::Connection type casting with default result type map can type cast #copy_data output with explicit decoder": "unknown", + "PG::Connection type casting with default result type map can process #copy_data output with row decoder and respects character encoding": "unknown", + "PG::Connection type casting with default result type map should respect a type mapping for result": "unknown", + "PG::Connection type casting with default result type map should work with arbitrary number of params in conjunction with type casting": "unknown", + "PG::Connection set_single_row_mode should receive rows before entire query is finished": "unknown", + "PG::Connection set_single_row_mode should receive rows before entire query fails": "unknown", + "PG::Connection deprecated forms of methods should forward send_query to send_query_params": "unknown", + "PG::Connection deprecated forms of methods should forward exec to exec_params": "unknown", +} + +var rubyPGIgnoreList21_1 = rubyPGIgnoreList20_2 + +var rubyPGIgnoreList20_2 = blocklist{} diff --git a/pkg/cmd/roachtest/ruby_pg_helpers.rb b/pkg/cmd/roachtest/ruby_pg_helpers.rb new file mode 100644 index 000000000000..f45c2a066720 --- /dev/null +++ b/pkg/cmd/roachtest/ruby_pg_helpers.rb @@ -0,0 +1,377 @@ +# This file was modified so setup_testing_db creates a CockroachDB instance +# with database "test". + +# -*- ruby -*- + +require 'pathname' +require 'rspec' +require 'shellwords' +require 'pg' + +DEFAULT_TEST_DIR_STR = File.join(Dir.pwd, "tmp_test_specs") +TEST_DIR_STR = ENV['RUBY_PG_TEST_DIR'] || DEFAULT_TEST_DIR_STR +TEST_DIRECTORY = Pathname.new(TEST_DIR_STR) + +module PG::TestingHelpers + + ### Automatically set up the database when it's used, and wrap a transaction around + ### examples that don't disable it. + def self::included( mod ) + super + + if mod.respond_to?( :around ) + + mod.before( :all ) { @conn = setup_testing_db(described_class ? described_class.name : mod.description) } + + mod.around( :each ) do |example| + begin + @conn.set_default_encoding + @conn.exec( 'BEGIN' ) unless example.metadata[:without_transaction] + desc = example.source_location.join(':') + @conn.exec %Q{SET application_name TO '%s'} % + [@conn.escape_string(desc.slice(-60))] + example.run + ensure + @conn.exec( 'ROLLBACK' ) unless example.metadata[:without_transaction] + end + end + + mod.after( :all ) { teardown_testing_db(@conn) } + end + + end + + + # + # Examples + # + + # Set some ANSI escape code constants (Shamelessly stolen from Perl's + # Term::ANSIColor by Russ Allbery and Zenin + ANSI_ATTRIBUTES = { + 'clear' => 0, + 'reset' => 0, + 'bold' => 1, + 'dark' => 2, + 'underline' => 4, + 'underscore' => 4, + 'blink' => 5, + 'reverse' => 7, + 'concealed' => 8, + + 'black' => 30, 'on_black' => 40, + 'red' => 31, 'on_red' => 41, + 'green' => 32, 'on_green' => 42, + 'yellow' => 33, 'on_yellow' => 43, + 'blue' => 34, 'on_blue' => 44, + 'magenta' => 35, 'on_magenta' => 45, + 'cyan' => 36, 'on_cyan' => 46, + 'white' => 37, 'on_white' => 47 + } + + + ############### + module_function + ############### + + ### Create a string that contains the ANSI codes specified and return it + def ansi_code( *attributes ) + attributes.flatten! + attributes.collect! {|at| at.to_s } + + return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM'] + attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';') + + # $stderr.puts " attr is: %p" % [attributes] + if attributes.empty? + return '' + else + return "\e[%sm" % attributes + end + end + + + ### Colorize the given +string+ with the specified +attributes+ and return it, handling + ### line-endings, color reset, etc. + def colorize( *args ) + string = '' + + if block_given? + string = yield + else + string = args.shift + end + + ending = string[/(\s)$/] || '' + string = string.rstrip + + return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending + end + + + ### Output a message with highlighting. + def message( *msg ) + $stderr.puts( colorize(:bold) { msg.flatten.join(' ') } ) + end + + + ### Output a logging message if $VERBOSE is true + def trace( *msg ) + return unless $VERBOSE + output = colorize( msg.flatten.join(' '), 'yellow' ) + $stderr.puts( output ) + end + + + ### Return the specified args as a string, quoting any that have a space. + def quotelist( *args ) + return args.flatten.collect {|part| part.to_s =~ /\s/ ? part.to_s.inspect : part.to_s } + end + + + ### Run the specified command +cmd+ with system(), failing if the execution + ### fails. + def run( *cmd ) + cmd.flatten! + + if cmd.length > 1 + trace( quotelist(*cmd) ) + else + trace( cmd ) + end + + system( *cmd ) + raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success? + end + + + ### Run the specified command +cmd+ after redirecting stdout and stderr to the specified + ### +logpath+, failing if the execution fails. + def log_and_run( logpath, *cmd ) + cmd.flatten! + + if cmd.length > 1 + trace( quotelist(*cmd) ) + else + trace( cmd ) + end + + # Eliminate the noise of creating/tearing down the database by + # redirecting STDERR/STDOUT to a logfile + logfh = File.open( logpath, File::WRONLY|File::CREAT|File::APPEND ) + system( *cmd, [STDOUT, STDERR] => logfh ) + + raise "Command failed: [%s]" % [cmd.join(' ')] unless $?.success? + end + + + ### Check the current directory for directories that look like they're + ### testing directories from previous tests, and tell any postgres instances + ### running in them to shut down. + def stop_existing_postmasters + # tmp_test_0.22329534700318 + pat = Pathname.getwd + 'tmp_test_*' + Pathname.glob( pat.to_s ).each do |testdir| + datadir = testdir + 'data' + pidfile = datadir + 'postmaster.pid' + if pidfile.exist? && pid = pidfile.read.chomp.to_i + trace "pidfile (%p) exists: %d" % [ pidfile, pid ] + begin + Process.kill( 0, pid ) + rescue Errno::ESRCH + trace "No postmaster running for %s" % [ datadir ] + # Process isn't alive, so don't try to stop it + else + trace "Stopping lingering database at PID %d" % [ pid ] + run 'pg_ctl', '-D', datadir.to_s, '-m', 'fast', 'stop' + end + else + trace "No pidfile (%p)" % [ pidfile ] + end + end + end + + + ### Set up a CockroachDB database instance for testing. + def setup_testing_db( description ) + require 'pg' + stop_existing_postmasters() + + trace "Setting up test database for #{description}" + @test_pgdata = TEST_DIRECTORY + 'data' + @test_pgdata.mkpath + + ENV['PGPORT'] ||= "26257" + @port = ENV['PGPORT'].to_i + ENV['PGHOST'] = 'localhost' + @conninfo = "host=localhost port=#{@port} dbname=test" + + @logfile = TEST_DIRECTORY + 'setup.log' + trace "Command output logged to #{@logfile}" + + begin + unless (@test_pgdata+"postgresql.conf").exist? + FileUtils.rm_rf( @test_pgdata, :verbose => $DEBUG ) + trace "GG" + trace "Running initdb" + end + + trace "Creating the test DB" + log_and_run @logfile, '/home/ubuntu/cockroach', 'sql', '--insecure', '-e', 'DROP DATABASE IF EXISTS test' + log_and_run @logfile, '/home/ubuntu/cockroach', 'sql', '--insecure', '-e', 'CREATE DATABASE test' + + rescue => err + $stderr.puts "%p during test setup: %s" % [ err.class, err.message ] + $stderr.puts "See #{@logfile} for details." + $stderr.puts err.backtrace if $DEBUG + fail + end + + conn = PG.connect( @conninfo ) + conn.set_notice_processor do |message| + $stderr.puts( description + ':' + message ) if $DEBUG + end + + return conn + end + + + def teardown_testing_db( conn ) + trace "Tearing down test database" + + if conn + check_for_lingering_connections( conn ) + conn.finish + end + + end + + + def check_for_lingering_connections( conn ) + conn.exec( "SELECT * FROM pg_stat_activity" ) do |res| + conns = res.find_all {|row| row['pid'].to_i != conn.backend_pid && ["client backend", nil].include?(row["backend_type"]) } + unless conns.empty? + puts "Lingering connections remain:" + conns.each do |row| + puts " [%s] {%s} %s -- %s" % row.values_at( 'pid', 'state', 'application_name', 'query' ) + end + end + end + end + + + # Retrieve the names of the column types of a given result set. + def result_typenames(res) + @conn.exec_params( "SELECT " + res.nfields.times.map{|i| "format_type($#{i*2+1},$#{i*2+2})"}.join(","), + res.nfields.times.flat_map{|i| [res.ftype(i), res.fmod(i)] } ). + values[0] + end + + + # A matcher for checking the status of a PG::Connection to ensure it's still + # usable. + class ConnStillUsableMatcher + + def initialize + @conn = nil + @problem = nil + end + + def matches?( conn ) + @conn = conn + @problem = self.check_for_problems + return @problem.nil? + end + + def check_for_problems + return "is finished" if @conn.finished? + return "has bad status" unless @conn.status == PG::CONNECTION_OK + return "has bad transaction status (%d)" % [ @conn.transaction_status ] unless + @conn.transaction_status.between?( PG::PQTRANS_IDLE, PG::PQTRANS_INTRANS ) + return "is not usable." unless self.can_exec_query? + return nil + end + + def can_exec_query? + @conn.send_query( "VALUES (1)" ) + @conn.get_last_result.values == [["1"]] + end + + def failure_message + return "expected %p to be usable, but it %s" % [ @conn, @problem ] + end + + def failure_message_when_negated + "expected %p not to be usable, but it still is" % [ @conn ] + end + + end + + + ### Return a ConnStillUsableMatcher to be used like: + ### + ### expect( pg_conn ).to still_be_usable + ### + def still_be_usable + return ConnStillUsableMatcher.new + end + + def wait_for_polling_ok(conn, meth = :connect_poll) + status = conn.send(meth) + + while status != PG::PGRES_POLLING_OK + if status == PG::PGRES_POLLING_READING + select( [conn.socket_io], [], [], 5.0 ) or + raise "Asynchronous connection timed out!" + + elsif status == PG::PGRES_POLLING_WRITING + select( [], [conn.socket_io], [], 5.0 ) or + raise "Asynchronous connection timed out!" + end + status = conn.send(meth) + end + end + + def wait_for_query_result(conn) + result = nil + loop do + # Buffer any incoming data on the socket until a full result is ready. + conn.consume_input + while conn.is_busy + select( [conn.socket_io], nil, nil, 5.0 ) or + raise "Timeout waiting for query response." + conn.consume_input + end + + # Fetch the next result. If there isn't one, the query is finished + result = conn.get_result || break + end + result + end + +end + + +RSpec.configure do |config| + config.include( PG::TestingHelpers ) + + config.run_all_when_everything_filtered = true + config.filter_run :focus + config.order = 'random' + config.mock_with( :rspec ) do |mock| + mock.syntax = :expect + end + + if RUBY_PLATFORM =~ /mingw|mswin/ + config.filter_run_excluding :unix + else + config.filter_run_excluding :windows + end + + config.filter_run_excluding( :postgresql_93 ) if PG.library_version < 90300 + config.filter_run_excluding( :postgresql_94 ) if PG.library_version < 90400 + config.filter_run_excluding( :postgresql_95 ) if PG.library_version < 90500 + config.filter_run_excluding( :postgresql_96 ) if PG.library_version < 90600 + config.filter_run_excluding( :postgresql_10 ) if PG.library_version < 100000 + config.filter_run_excluding( :postgresql_12 ) if PG.library_version < 120000 +end diff --git a/pkg/testutils/lint/lint_test.go b/pkg/testutils/lint/lint_test.go index 60f9bedd0508..1bb9f7439d50 100644 --- a/pkg/testutils/lint/lint_test.go +++ b/pkg/testutils/lint/lint_test.go @@ -1282,6 +1282,7 @@ func TestLint(t *testing.T) { stream.GrepNot(`^sql/logictest/testdata/logic_test/pg_extension$`), stream.GrepNot(`^sql/opt/testutils/opttester/testfixtures/.*`), stream.GrepNot(`^util/timeutil/lowercase_timezones_generated.go$`), + stream.GrepNot(`^cmd/roachtest/ruby_pg_blocklist.go$`), stream.Map(func(s string) string { return filepath.Join(pkgDir, s) }),