diff --git a/src/extension.c b/src/extension.c index bd0986d8c3f..f57cfe3943c 100644 --- a/src/extension.c +++ b/src/extension.c @@ -353,3 +353,20 @@ ts_extension_is_proxy_table_relid(Oid relid) { return relid == extension_proxy_oid; } + +#if TS_DEBUG +static const char *extstate_str[] = { + [EXTENSION_STATE_UNKNOWN] = "unknown", + [EXTENSION_STATE_TRANSITIONING] = "transitioning", + [EXTENSION_STATE_CREATED] = "created", + [EXTENSION_STATE_NOT_INSTALLED] = "not installed", +}; + +TS_FUNCTION_INFO_V1(ts_extension_get_state); + +Datum +ts_extension_get_state(PG_FUNCTION_ARGS) +{ + PG_RETURN_TEXT_P(cstring_to_text(extstate_str[extstate])); +} +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e4bd594eab0..882db243c8b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -75,6 +75,24 @@ elseif(REQUIRE_ALL_TESTS) "All tests were required but 'pg_isolation_regress' could not be found") endif() +if(TAP_CHECKS) + add_custom_target( + provecheck + COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/tmp_check + COMMAND + CONFDIR=${CMAKE_BINARY_DIR}/test PATH="${PG_BINDIR}:$ENV{PATH}" + PG_REGRESS=${PG_REGRESS} SRC_DIR=${PG_SOURCE_DIR} + CM_SRC_DIR=${CMAKE_SOURCE_DIR} PG_LIBDIR=${PG_LIBDIR} + ${PRIMARY_TEST_DIR}/pg_prove.sh + USES_TERMINAL) + list(APPEND _install_checks provecheck) +elseif(REQUIRE_ALL_TESTS) + message( + FATAL_ERROR + "All tests were required but TAP_CHECKS was off (see previous messages why)" + ) +endif() + # We add the installcheck target even when _install_checks is empty as tsl code # might add dependencies to it even when regress checks are disabled. add_custom_target(installcheck DEPENDS ${_install_checks}) @@ -94,6 +112,7 @@ add_custom_target(installchecklocal DEPENDS ${_local_install_checks}) add_subdirectory(sql) add_subdirectory(isolation) +add_subdirectory(t) if(PG_SOURCE_DIR) add_subdirectory(pgtest) diff --git a/test/t/001_extension.pl b/test/t/001_extension.pl new file mode 100644 index 00000000000..7472f3ccb30 --- /dev/null +++ b/test/t/001_extension.pl @@ -0,0 +1,97 @@ +# This file and its contents are licensed under the Timescale License. +# Please see the included NOTICE for copyright information and +# LICENSE-TIMESCALE for a copy of the license. + +use strict; +use warnings; +use TimescaleNode; +use TestLib; +use Data::Dumper; +use Test::More tests => 6; + +# This test checks that the extension state is handled correctly +# across multiple sessions. Specifically, if the extension state +# changes in one session (e.g., the extension is created or dropped), +# this should be reflected in other concurrent sessions. +# +# To test this, we start one interactive "background" psql session +# that stays open for the duration of the tests and then change the +# extension state from other sessions. +my $node = TimescaleNode->create('extension'); +my $in = ''; +my $out = ''; +my $timer = IPC::Run::timeout(180); +my $h = $node->interactive_psql('postgres', \$in, \$out, $timer, on_error_stop => 0); + +sub check_extension_state +{ + my ($pattern, $annotation) = @_; + + # report test failures from caller location + local $Test::Builder::Level = $Test::Builder::Level + 1; + + # reset output collector + $out = ""; + # restart per-command timer + $timer->start(5); + # send the data to be sent + $in .= "SELECT * FROM extension_state();\n"; + # wait ... + + pump $h until ($out =~ $pattern || $timer->is_expired); + my $okay = ($out =~ $pattern && !$timer->is_expired); + ok($okay, $annotation); + # for debugging, log actual output if it didn't match + local $Data::Dumper::Terse = 1; + local $Data::Dumper::Useqq = 1; + diag 'Actual output was ' . Dumper($out) . "Did not match \"$pattern\"\n" + if !$okay; + + # Read the next command prompt to prepare for next command + $out = ""; + pump $h until ($out =~ qr/postgres=# / || $timer->is_expired); + return; +} + +# Create extension_state function and check initial state +my $result = $node->safe_psql('postgres', +q{ + \ir t/functions.sql + SELECT * FROM extension_state(); +}); + +# Initially, the state should be "created" in both sessions +is($result, qq/created/, "initial state is \"created\""); +check_extension_state(qr/created/, "initial state is \"created\""); + +# Drop the extension in one session, and the new state should be +# reflected in the other backend. +$result = $node->safe_psql('postgres', q{ + DROP EXTENSION timescaledb; + SELECT * FROM extension_state(); +}); + +# After dropping the extension, the new state in both sessions should +# be "unknown" +is($result, qq/unknown/, "state after dropped is \"unknown\""); +check_extension_state(qr/unknown/, "state is \"unknown\" after extension is dropped in other backend"); + +# Create the extension again, which should be reflected in both +# sessions. +$result = $node->safe_psql('postgres', q{ + CREATE EXTENSION timescaledb; + SELECT * FROM extension_state(); +}); + +# After creating the extension again in one session, the other session +# should go back to "created" state. +is($result, qq/created/, "state after creating extension is \"created\""); +check_extension_state(qr/created/, "state is \"created\" after extension is created in other backend"); + +# Quit the interactive psql session +$in .= q{ + \q +}; + +$h->finish or die "psql returned $?"; +$node->stop; diff --git a/test/t/CMakeLists.txt b/test/t/CMakeLists.txt new file mode 100644 index 00000000000..6ce88cec66f --- /dev/null +++ b/test/t/CMakeLists.txt @@ -0,0 +1,12 @@ +set(PROVE_TEST_FILES 001_extension.pl) + +if(CMAKE_BUILD_TYPE MATCHES Debug) + list(APPEND PROVE_TEST_FILES ${PROVE_DEBUG_TEST_FILES}) +endif(CMAKE_BUILD_TYPE MATCHES Debug) + +foreach(P_FILE ${PROVE_TEST_FILES}) + configure_file(${P_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${P_FILE} COPYONLY) +endforeach(P_FILE) + +set(MODULE_PATHNAME "$libdir/timescaledb-${PROJECT_VERSION_MOD}") +configure_file(functions.sql.in functions.sql) diff --git a/test/t/functions.sql.in b/test/t/functions.sql.in new file mode 100644 index 00000000000..1c087d36a65 --- /dev/null +++ b/test/t/functions.sql.in @@ -0,0 +1,2 @@ +CREATE FUNCTION extension_state() RETURNS TEXT +AS '@MODULE_PATHNAME@', 'ts_extension_get_state' LANGUAGE C; diff --git a/tsl/test/CMakeLists.txt b/tsl/test/CMakeLists.txt index e8dc3884cf8..b9d763e71f1 100644 --- a/tsl/test/CMakeLists.txt +++ b/tsl/test/CMakeLists.txt @@ -76,7 +76,7 @@ endif() if(TAP_CHECKS) add_custom_target( - provecheck + provecheck-t COMMAND rm -rf ${CMAKE_CURRENT_BINARY_DIR}/tmp_check COMMAND CONFDIR=${CMAKE_BINARY_DIR}/tsl/test PATH="${PG_BINDIR}:$ENV{PATH}" @@ -84,7 +84,7 @@ if(TAP_CHECKS) CM_SRC_DIR=${CMAKE_SOURCE_DIR} PG_LIBDIR=${PG_LIBDIR} ${PRIMARY_TEST_DIR}/pg_prove.sh USES_TERMINAL) - list(APPEND _install_checks provecheck) + list(APPEND _install_checks provecheck-t) elseif(REQUIRE_ALL_TESTS) message( FATAL_ERROR