From cb54295f1629335c516c5e3810fcca39dc3b3ec2 Mon Sep 17 00:00:00 2001 From: Elliot Chance Date: Sun, 15 Oct 2023 17:06:53 -0400 Subject: [PATCH] ci: Run tests against any version of V There are some minor syntax changes to bring it up to V 0.4.2 4bc9a8f. This also adds a new environment variable that lets you run tests at any version (tag or commit), such as: OLDV=0.3.5 make sql-test --- Makefile | 34 +++++++++++++++++++++++++++------- docs/testing.rst | 15 +++++++++++++++ docs/v-client-library-docs.rst | 8 ++++---- vsql/btree.v | 4 ++-- vsql/btree_test.v | 4 ++-- vsql/connection.v | 14 ++++++-------- vsql/eval.v | 2 +- vsql/funcs.v | 2 +- vsql/lexer.v | 4 ++-- vsql/pager.v | 2 +- vsql/query_cache.v | 2 +- vsql/server.v | 5 +++-- vsql/sql_test.v | 2 +- vsql/time.v | 10 +++++++--- vsql/utils.v | 2 +- vsql/value.v | 4 ++-- vsql/virtual_table.v | 2 +- vsql/walk.v | 10 ++++++---- 18 files changed, 83 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 9500b4c..7b96861 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: bench bench-on-disk bench-memory fmt fmt-verify test examples grammar sql-test docs clean-docs +.PHONY: bench bench-on-disk bench-memory fmt fmt-verify test examples grammar sql-test docs clean-docs oldv # Is not used at the moment. It is useful for testing options like different # `-gc` values. @@ -6,6 +6,16 @@ BUILD_OPTIONS = PROD = -prod +# OLDV let's you specify a differnt V version rather than the default one. This +# is important for testing V language changes or legacy performance tests. It is +# documented in testing.rst. +ifdef OLDV +V_DIR := $(shell dirname `which v`) +V := "/tmp/oldv/v_at_$(OLDV)/v" +else +V := $(shell which v) +endif + # Ready is a some quick tasks that can easily be forgotten when preparing a # diff. @@ -22,6 +32,16 @@ bin/vsql.exe: v $(BUILD_OPTIONS) $(PROD) cmd/vsql mv cmd/vsql/vsql.exe bin/vsql.exe +oldv: +ifdef OLDV + @mkdir -p /tmp/oldv/ + @# VJOBS and VFLAGS needs to be provided for macOS. I'm not sure if they also + @# have to be removed for linux. + cd $(V_DIR) && VJOBS=1 VFLAGS='-no-parallel' cmd/tools/oldv $(OLDV) -w /tmp/oldv/ + @# Print out the version to make sure the V build was successful. + $(V) version +endif + # Documentation snippets: @@ -50,14 +70,14 @@ fmt-verify: # Tests -test: - v -stats $(BUILD_OPTIONS) $(PROD) test vsql +test: oldv + $(V) -stats $(BUILD_OPTIONS) $(PROD) test vsql -btree-test: - v -stats $(BUILD_OPTIONS) $(PROD) test vsql/btree_test.v +btree-test: oldv + $(V) -stats $(BUILD_OPTIONS) $(PROD) test vsql/btree_test.v -sql-test: - v -stats $(BUILD_OPTIONS) test vsql/sql_test.v +sql-test: oldv + $(V) -stats $(BUILD_OPTIONS) test vsql/sql_test.v # CLI Tests diff --git a/docs/testing.rst b/docs/testing.rst index d934ff8..8ea5919 100644 --- a/docs/testing.rst +++ b/docs/testing.rst @@ -324,3 +324,18 @@ Running the specific test again can be done with: .. code-block:: sh TEST=subquery:32 make sql-test + +Using Different V Versions +-------------------------- + +Sometimes there are V language changes which might break tests, or otherwise +cause issues on newer versions. Fortunatly there is a `oldv` tool which can be +used to compile older version of `v` for testing. You can run tests simply by +supplying a different version of V: + +.. code-block:: sh + + OLDV=0.3.5 make sql-test + +You can use any commit or tag for ``OLDV``. All tags can be +`found here `_. diff --git a/docs/v-client-library-docs.rst b/docs/v-client-library-docs.rst index ba818dc..b35f15d 100644 --- a/docs/v-client-library-docs.rst +++ b/docs/v-client-library-docs.rst @@ -335,8 +335,8 @@ enum Boolean pub enum Boolean { // These must not be negative values because they are encoded as u8 on disk. is_unknown = 0 // same as NULL - is_false = 1 - is_true = 2 + is_false = 1 + is_true = 2 } @@ -351,7 +351,7 @@ struct VirtualTable pub struct VirtualTable { create_table_sql string create_table_stmt CreateTableStmt - data VirtualTableProviderFn + data VirtualTableProviderFn [required] mut: is_done bool rows []Row @@ -413,7 +413,7 @@ struct Connection // now allows you to override the wall clock that is used. The Time must be // in UTC with a separate offset for the current local timezone (in positive // or negative minutes). - now fn () (time.Time, i16) + now fn () (time.Time, i16) [required] // warnings are SQLSTATE errors that do not stop the execution. For example, // if a value must be truncated during a runtime CAST. // diff --git a/vsql/btree.v b/vsql/btree.v index 945e710..d0675d8 100644 --- a/vsql/btree.v +++ b/vsql/btree.v @@ -272,7 +272,7 @@ fn (mut p Btree) split_page(path []int, page Page, obj PageObject, kind u8) ! { mut inserted_at := -1 for pos, object in objects { if compare_bytes(obj.key, object.key) <= 0 { - mut new_objects := objects[..pos] + mut new_objects := objects[..pos].clone() new_objects << obj new_objects << objects[pos..] objects = new_objects.clone() @@ -362,7 +362,7 @@ fn (mut p Btree) split_page(path []int, page Page, obj PageObject, kind u8) ! { // usually quite small. However, they are not a fixed size because the // keys can be variable length. if page3.used > p.page_size - p2.length() { - mut new_path := path[..path.len - 1] + mut new_path := path[..path.len - 1].clone() p.split_page(new_path, page3, p2, kind_not_leaf)! } else { page3.add(p2)! diff --git a/vsql/btree_test.v b/vsql/btree_test.v index 1ca7440..3cbf1dd 100644 --- a/vsql/btree_test.v +++ b/vsql/btree_test.v @@ -18,10 +18,10 @@ fn test_btree_test() ! { blob_sizes := [ // 48 means all objects will fit in pages (and several per page) and // never have to use blob storage. - 48 + 48, // 148 is larger than half a page so we always end up with one object // per page, but no need to use blob storage, yet. - 148 + 148, // 348 is larger than a page so all items must go into blob storage. 348, ] diff --git a/vsql/connection.v b/vsql/connection.v index 0eb5a4f..3dcd6aa 100644 --- a/vsql/connection.v +++ b/vsql/connection.v @@ -51,7 +51,7 @@ pub mut: // now allows you to override the wall clock that is used. The Time must be // in UTC with a separate offset for the current local timezone (in positive // or negative minutes). - now fn () (time.Time, i16) + now fn () (time.Time, i16) [required] // warnings are SQLSTATE errors that do not stop the execution. For example, // if a value must be truncated during a runtime CAST. // @@ -116,9 +116,7 @@ fn open_connection(path string, options ConnectionOptions) !&Connection { query_cache: options.query_cache current_catalog: catalog_name current_schema: vsql.default_schema_name - now: fn () (time.Time, i16) { - return time.utc(), i16(time.offset() / 60) - } + now: default_now } register_builtin_funcs(mut conn)! @@ -244,9 +242,9 @@ fn (mut c CatalogConnection) release_read_connection() { // prepare returns a precompiled statement that can be executed multiple times // with different provided parameters. -pub fn (mut c Connection) prepare(sql string) !PreparedStmt { +pub fn (mut c Connection) prepare(sql_stmt string) !PreparedStmt { t := start_timer() - stmt, params, explain := c.query_cache.parse(sql) or { + stmt, params, explain := c.query_cache.parse(sql_stmt) or { mut catalog := c.catalog() catalog.storage.transaction_aborted() return err @@ -257,14 +255,14 @@ pub fn (mut c Connection) prepare(sql string) !PreparedStmt { } // query executes a statement. If there is a result set it will be returned. -pub fn (mut c Connection) query(sql string) !Result { +pub fn (mut c Connection) query(sql_stmt string) !Result { mut catalog := c.catalog() if catalog.storage.transaction_state == .aborted { return sqlstate_25p02() } - mut prepared := c.prepare(sql) or { + mut prepared := c.prepare(sql_stmt) or { catalog.storage.transaction_aborted() return err } diff --git a/vsql/eval.v b/vsql/eval.v index 2b841b7..ac67692 100644 --- a/vsql/eval.v +++ b/vsql/eval.v @@ -293,7 +293,7 @@ fn time_value(conn &Connection, prec int, include_offset bool) string { mut s := now.strftime('%H:%M:%S') if prec > 0 { - microseconds := left_pad(now.microsecond.str(), '0', 6) + microseconds := left_pad(int(now.nanosecond / 1000).str(), '0', 6) s += '.' + microseconds.substr(0, prec) } diff --git a/vsql/funcs.v b/vsql/funcs.v index 3f53661..e209f17 100644 --- a/vsql/funcs.v +++ b/vsql/funcs.v @@ -6,7 +6,7 @@ struct Func { name string arg_types []Type is_agg bool - func fn ([]Value) !Value + func fn ([]Value) !Value [required] return_type Type } diff --git a/vsql/lexer.v b/vsql/lexer.v index 1daf37c..59b4932 100644 --- a/vsql/lexer.v +++ b/vsql/lexer.v @@ -35,9 +35,9 @@ pub: value string } -fn tokenize(sql string) []Token { +fn tokenize(sql_stmt string) []Token { mut tokens := []Token{} - cs := sql.trim(';').runes() + cs := sql_stmt.trim(';').runes() mut i := 0 next: for i < cs.len { diff --git a/vsql/pager.v b/vsql/pager.v index 09797c9..3f875ba 100644 --- a/vsql/pager.v +++ b/vsql/pager.v @@ -109,7 +109,7 @@ fn (mut p FilePager) fetch_page(page_number int) !Page { return Page{ kind: b.read_u8() used: b.read_u16() - data: buf[page_header_size..] + data: buf[page_header_size..].clone() } } diff --git a/vsql/query_cache.v b/vsql/query_cache.v index c8007b8..0cc5c76 100644 --- a/vsql/query_cache.v +++ b/vsql/query_cache.v @@ -107,7 +107,7 @@ fn (mut q QueryCache) parse(query string) !(Stmt, map[string]Value, bool) { mut explain := false if tokens[0].value.to_upper() == 'EXPLAIN' { explain = true - tokens = tokens[1..] + tokens = tokens[1..].clone() } key, params, new_tokens := q.prepare(tokens) diff --git a/vsql/server.v b/vsql/server.v index bc7ea55..6327c36 100644 --- a/vsql/server.v +++ b/vsql/server.v @@ -34,6 +34,7 @@ pub fn new_server(options ServerOptions) Server { return Server{options, &Connection{ query_cache: new_query_cache() + now: default_now catalogs: { catalog_name: catalog } @@ -95,7 +96,7 @@ fn (mut s Server) handle_conn(mut c net.TcpConn) ! { for { msg_type := conn.read_byte()! match msg_type { - `Q` /* Query */ { + `Q` { // Query mut query := conn.read_query()! // A missing "FROM" clause is valid in PostgreSQL, but it's not @@ -126,7 +127,7 @@ fn (mut s Server) handle_conn(mut c net.TcpConn) ! { conn.write_ready_for_query()! } - `X` /* Terminate */ { + `X` { // Terminate // Don't bother consuming the message since we're going to // disconnect anyway. break diff --git a/vsql/sql_test.v b/vsql/sql_test.v index 4be6089..6edc7b3 100644 --- a/vsql/sql_test.v +++ b/vsql/sql_test.v @@ -138,7 +138,7 @@ fn run_single_test(test SQLTest, query_cache &QueryCache, verbose bool, filter_l hour: 14 minute: 5 second: 3 - microsecond: 120056 + nanosecond: 120056000 }), 300 } register_pg_functions(mut db)! diff --git a/vsql/time.v b/vsql/time.v index 3b0898f..d487ba6 100644 --- a/vsql/time.v +++ b/vsql/time.v @@ -154,7 +154,7 @@ fn new_time_from_components(typ Type, year int, month int, day int, hour int, mi hour: hour minute: minute second: second - microsecond: microsecond + nanosecond: microsecond * 1000 })} } @@ -237,7 +237,7 @@ fn (t Time) i64() i64 { // See i64() for details. fn (t Time) time_i64() i64 { return t.t.hour * vsql.hour_period + t.t.minute * vsql.minute_period + - t.t.second * vsql.second_period + t.t.microsecond + t.t.second * vsql.second_period + int(t.t.nanosecond / 1000) } // See i64() for details. @@ -312,7 +312,7 @@ fn (t Time) str_fractional_seconds(prec int) string { } // Round down first, if needed. - mut s := t.t.microsecond.str() + mut s := int(t.t.nanosecond / 1000).str() if prec < s.len { s = s[..prec] } @@ -392,3 +392,7 @@ fn time_zone_value(conn &Connection) string { return s } + +fn default_now() (time.Time, i16) { + return time.utc(), i16(time.offset() / 60) +} diff --git a/vsql/utils.v b/vsql/utils.v index a8b5a58..e822400 100644 --- a/vsql/utils.v +++ b/vsql/utils.v @@ -18,7 +18,7 @@ fn compare_bytes(a []u8, b []u8) int { for i in 0 .. min { if a[i] != b[i] { - return a[i] - b[i] + return int(a[i]) - int(b[i]) } } diff --git a/vsql/value.v b/vsql/value.v index 06b25cc..0c07c83 100644 --- a/vsql/value.v +++ b/vsql/value.v @@ -12,8 +12,8 @@ import regex pub enum Boolean { // These must not be negative values because they are encoded as u8 on disk. is_unknown = 0 // same as NULL - is_false = 1 - is_true = 2 + is_false = 1 + is_true = 2 } // Returns ``TRUE``, ``FALSE`` or ``UNKNOWN``. diff --git a/vsql/virtual_table.v b/vsql/virtual_table.v index 9e9d8a5..2e25706 100644 --- a/vsql/virtual_table.v +++ b/vsql/virtual_table.v @@ -6,7 +6,7 @@ type VirtualTableProviderFn = fn (mut t VirtualTable) ! pub struct VirtualTable { create_table_sql string create_table_stmt CreateTableStmt - data VirtualTableProviderFn + data VirtualTableProviderFn [required] mut: is_done bool rows []Row diff --git a/vsql/walk.v b/vsql/walk.v index a32a1e8..e1f2ee9 100644 --- a/vsql/walk.v +++ b/vsql/walk.v @@ -33,7 +33,7 @@ fn (mut iter PageIterator) next() ?PageObject { // Load all the objects for this leaf. Making sure to skip over any keys // that are out of bounds. - iter.objects = (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1])!).objects() + iter.objects = (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1]) or { return none }).objects() for object in iter.objects { // TODO(elliotchance): It would be more efficient to do a binary // search here since the page is already sorted. @@ -55,20 +55,22 @@ fn (mut iter PageIterator) next() ?PageObject { iter.depth_iterator = iter.depth_iterator[..iter.depth_iterator.len - 1] iter.depth_iterator[iter.depth_iterator.len - 1]++ - if iter.depth_iterator[iter.depth_iterator.len - 1] < (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1])!).objects().len { + if iter.depth_iterator[iter.depth_iterator.len - 1] < (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1]) or { + return none + }).objects().len { break } } for (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1])!).kind == kind_not_leaf { - objects := (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1])!).objects() + objects := (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1]) or { return none }).objects() mut buf := new_bytes(objects[iter.depth_iterator[iter.depth_iterator.len - 1]].value) iter.path << buf.read_i32() iter.depth_iterator << 0 } - iter.objects = (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1])!).objects() + iter.objects = (iter.btree.pager.fetch_page(iter.path[iter.path.len - 1]) or { return none }).objects() } o := iter.objects[iter.depth_iterator[iter.depth_iterator.len - 1]]