1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/os/persistentdata/persistentstorage/sqlite3api/TEST/TclScript/select9.test Fri Jun 15 03:10:57 2012 +0200
1.3 @@ -0,0 +1,421 @@
1.4 +# 2008 June 24
1.5 +#
1.6 +# The author disclaims copyright to this source code. In place of
1.7 +# a legal notice, here is a blessing:
1.8 +#
1.9 +# May you do good and not evil.
1.10 +# May you find forgiveness for yourself and forgive others.
1.11 +# May you share freely, never taking more than you give.
1.12 +#
1.13 +#***********************************************************************
1.14 +# This file implements regression tests for SQLite library.
1.15 +#
1.16 +# $Id: select9.test,v 1.4 2008/07/01 14:39:35 danielk1977 Exp $
1.17 +
1.18 +# The tests in this file are focused on test compound SELECT statements
1.19 +# that have any or all of an ORDER BY, LIMIT or OFFSET clauses. As of
1.20 +# version 3.6.0, SQLite contains code to use SQL indexes where possible
1.21 +# to optimize such statements.
1.22 +#
1.23 +
1.24 +# TODO Points:
1.25 +#
1.26 +# * Are there any "column affinity" issues to consider?
1.27 +
1.28 +set testdir [file dirname $argv0]
1.29 +source $testdir/tester.tcl
1.30 +
1.31 +#set ISQUICK 1
1.32 +
1.33 +#-------------------------------------------------------------------------
1.34 +# test_compound_select TESTNAME SELECT RESULT
1.35 +#
1.36 +# This command is used to run multiple LIMIT/OFFSET test cases based on
1.37 +# the single SELECT statement passed as the second argument. The SELECT
1.38 +# statement may not contain a LIMIT or OFFSET clause. This proc tests
1.39 +# many statements of the form:
1.40 +#
1.41 +# "$SELECT limit $X offset $Y"
1.42 +#
1.43 +# for various values of $X and $Y.
1.44 +#
1.45 +# The third argument, $RESULT, should contain the expected result of
1.46 +# the command [execsql $SELECT].
1.47 +#
1.48 +# The first argument, $TESTNAME, is used as the base test case name to
1.49 +# pass to [do_test] for each individual LIMIT OFFSET test case.
1.50 +#
1.51 +proc test_compound_select {testname sql result} {
1.52 +
1.53 + set nCol 1
1.54 + db eval $sql A {
1.55 + set nCol [llength $A(*)]
1.56 + break
1.57 + }
1.58 + set nRow [expr {[llength $result] / $nCol}]
1.59 +
1.60 + set ::compound_sql $sql
1.61 + do_test $testname {
1.62 + execsql $::compound_sql
1.63 + } $result
1.64 +#return
1.65 +
1.66 + set iLimitIncr 1
1.67 + set iOffsetIncr 1
1.68 + if {[info exists ::ISQUICK] && $::ISQUICK && $nRow>=5} {
1.69 + set iOffsetIncr [expr $nRow / 5]
1.70 + set iLimitIncr [expr $nRow / 5]
1.71 + }
1.72 +
1.73 + set iLimitEnd [expr $nRow+$iLimitIncr]
1.74 + set iOffsetEnd [expr $nRow+$iOffsetIncr]
1.75 +
1.76 + for {set iOffset 0} {$iOffset < $iOffsetEnd} {incr iOffset $iOffsetIncr} {
1.77 + for {set iLimit 0} {$iLimit < $iLimitEnd} {incr iLimit} {
1.78 +
1.79 + set ::compound_sql "$sql LIMIT $iLimit"
1.80 + if {$iOffset != 0} {
1.81 + append ::compound_sql " OFFSET $iOffset"
1.82 + }
1.83 +
1.84 + set iStart [expr {$iOffset*$nCol}]
1.85 + set iEnd [expr {($iOffset*$nCol) + ($iLimit*$nCol) -1}]
1.86 +
1.87 + do_test $testname.limit=$iLimit.offset=$iOffset {
1.88 + execsql $::compound_sql
1.89 + } [lrange $result $iStart $iEnd]
1.90 + }
1.91 + }
1.92 +}
1.93 +
1.94 +#-------------------------------------------------------------------------
1.95 +# test_compound_select_flippable TESTNAME SELECT RESULT
1.96 +#
1.97 +# This command is for testing statements of the form:
1.98 +#
1.99 +# <simple select 1> <compound op> <simple select 2> ORDER BY <order by>
1.100 +#
1.101 +# where each <simple select> is a simple (non-compound) select statement
1.102 +# and <compound op> is one of "INTERSECT", "UNION ALL" or "UNION".
1.103 +#
1.104 +# This proc calls [test_compound_select] twice, once with the select
1.105 +# statement as it is passed to this command, and once with the positions
1.106 +# of <select statement 1> and <select statement 2> exchanged.
1.107 +#
1.108 +proc test_compound_select_flippable {testname sql result} {
1.109 + test_compound_select $testname $sql $result
1.110 +
1.111 + set select [string trim $sql]
1.112 + set RE {(.*)(UNION ALL|INTERSECT|UNION)(.*)(ORDER BY.*)}
1.113 + set rc [regexp $RE $select -> s1 op s2 order_by]
1.114 + if {!$rc} {error "Statement is unflippable: $select"}
1.115 +
1.116 + set flipsql "$s2 $op $s1 $order_by"
1.117 + test_compound_select $testname.flipped $flipsql $result
1.118 +}
1.119 +
1.120 +#############################################################################
1.121 +# Begin tests.
1.122 +#
1.123 +
1.124 +# Create and populate a sample database.
1.125 +#
1.126 +do_test select9-1.0 {
1.127 + execsql {
1.128 + CREATE TABLE t1(a, b, c);
1.129 + CREATE TABLE t2(d, e, f);
1.130 + BEGIN;
1.131 + INSERT INTO t1 VALUES(1, 'one', 'I');
1.132 + INSERT INTO t1 VALUES(3, NULL, NULL);
1.133 + INSERT INTO t1 VALUES(5, 'five', 'V');
1.134 + INSERT INTO t1 VALUES(7, 'seven', 'VII');
1.135 + INSERT INTO t1 VALUES(9, NULL, NULL);
1.136 + INSERT INTO t1 VALUES(2, 'two', 'II');
1.137 + INSERT INTO t1 VALUES(4, 'four', 'IV');
1.138 + INSERT INTO t1 VALUES(6, NULL, NULL);
1.139 + INSERT INTO t1 VALUES(8, 'eight', 'VIII');
1.140 + INSERT INTO t1 VALUES(10, 'ten', 'X');
1.141 +
1.142 + INSERT INTO t2 VALUES(1, 'two', 'IV');
1.143 + INSERT INTO t2 VALUES(2, 'four', 'VIII');
1.144 + INSERT INTO t2 VALUES(3, NULL, NULL);
1.145 + INSERT INTO t2 VALUES(4, 'eight', 'XVI');
1.146 + INSERT INTO t2 VALUES(5, 'ten', 'XX');
1.147 + INSERT INTO t2 VALUES(6, NULL, NULL);
1.148 + INSERT INTO t2 VALUES(7, 'fourteen', 'XXVIII');
1.149 + INSERT INTO t2 VALUES(8, 'sixteen', 'XXXII');
1.150 + INSERT INTO t2 VALUES(9, NULL, NULL);
1.151 + INSERT INTO t2 VALUES(10, 'twenty', 'XL');
1.152 +
1.153 + COMMIT;
1.154 + }
1.155 +} {}
1.156 +
1.157 +# Each iteration of this loop runs the same tests with a different set
1.158 +# of indexes present within the database schema. The data returned by
1.159 +# the compound SELECT statements in the test cases should be the same
1.160 +# in each case.
1.161 +#
1.162 +set iOuterLoop 1
1.163 +foreach indexes [list {
1.164 + /* Do not create any indexes. */
1.165 +} {
1.166 + CREATE INDEX i1 ON t1(a)
1.167 +} {
1.168 + CREATE INDEX i2 ON t1(b)
1.169 +} {
1.170 + CREATE INDEX i3 ON t2(d)
1.171 +} {
1.172 + CREATE INDEX i4 ON t2(e)
1.173 +}] {
1.174 +
1.175 + do_test select9-1.$iOuterLoop.1 {
1.176 + execsql $indexes
1.177 + } {}
1.178 +
1.179 + # Test some 2-way UNION ALL queries. No WHERE clauses.
1.180 + #
1.181 + test_compound_select select9-1.$iOuterLoop.2 {
1.182 + SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2
1.183 + } {1 one 3 {} 5 five 7 seven 9 {} 2 two 4 four 6 {} 8 eight 10 ten 1 two 2 four 3 {} 4 eight 5 ten 6 {} 7 fourteen 8 sixteen 9 {} 10 twenty}
1.184 + test_compound_select select9-1.$iOuterLoop.3 {
1.185 + SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 1
1.186 + } {1 one 1 two 2 two 2 four 3 {} 3 {} 4 four 4 eight 5 five 5 ten 6 {} 6 {} 7 seven 7 fourteen 8 eight 8 sixteen 9 {} 9 {} 10 ten 10 twenty}
1.187 + test_compound_select select9-1.$iOuterLoop.4 {
1.188 + SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 2
1.189 + } {3 {} 9 {} 6 {} 3 {} 6 {} 9 {} 8 eight 4 eight 5 five 4 four 2 four 7 fourteen 1 one 7 seven 8 sixteen 10 ten 5 ten 10 twenty 2 two 1 two}
1.190 + test_compound_select_flippable select9-1.$iOuterLoop.5 {
1.191 + SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 1, 2
1.192 + } {1 one 1 two 2 four 2 two 3 {} 3 {} 4 eight 4 four 5 five 5 ten 6 {} 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 9 {} 10 ten 10 twenty}
1.193 + test_compound_select_flippable select9-1.$iOuterLoop.6 {
1.194 + SELECT a, b FROM t1 UNION ALL SELECT d, e FROM t2 ORDER BY 2, 1
1.195 + } {3 {} 3 {} 6 {} 6 {} 9 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
1.196 +
1.197 + # Test some 2-way UNION queries.
1.198 + #
1.199 + test_compound_select select9-1.$iOuterLoop.7 {
1.200 + SELECT a, b FROM t1 UNION SELECT d, e FROM t2
1.201 + } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
1.202 +
1.203 + test_compound_select select9-1.$iOuterLoop.8 {
1.204 + SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 1
1.205 + } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
1.206 +
1.207 + test_compound_select select9-1.$iOuterLoop.9 {
1.208 + SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 2
1.209 + } {3 {} 6 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
1.210 +
1.211 + test_compound_select_flippable select9-1.$iOuterLoop.10 {
1.212 + SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 1, 2
1.213 + } {1 one 1 two 2 four 2 two 3 {} 4 eight 4 four 5 five 5 ten 6 {} 7 fourteen 7 seven 8 eight 8 sixteen 9 {} 10 ten 10 twenty}
1.214 +
1.215 + test_compound_select_flippable select9-1.$iOuterLoop.11 {
1.216 + SELECT a, b FROM t1 UNION SELECT d, e FROM t2 ORDER BY 2, 1
1.217 + } {3 {} 6 {} 9 {} 4 eight 8 eight 5 five 2 four 4 four 7 fourteen 1 one 7 seven 8 sixteen 5 ten 10 ten 10 twenty 1 two 2 two}
1.218 +
1.219 + # Test some 2-way INTERSECT queries.
1.220 + #
1.221 + test_compound_select select9-1.$iOuterLoop.11 {
1.222 + SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2
1.223 + } {3 {} 6 {} 9 {}}
1.224 + test_compound_select_flippable select9-1.$iOuterLoop.12 {
1.225 + SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 1
1.226 + } {3 {} 6 {} 9 {}}
1.227 + test_compound_select select9-1.$iOuterLoop.13 {
1.228 + SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 2
1.229 + } {3 {} 6 {} 9 {}}
1.230 + test_compound_select_flippable select9-1.$iOuterLoop.14 {
1.231 + SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 2, 1
1.232 + } {3 {} 6 {} 9 {}}
1.233 + test_compound_select_flippable select9-1.$iOuterLoop.15 {
1.234 + SELECT a, b FROM t1 INTERSECT SELECT d, e FROM t2 ORDER BY 1, 2
1.235 + } {3 {} 6 {} 9 {}}
1.236 +
1.237 + # Test some 2-way EXCEPT queries.
1.238 + #
1.239 + test_compound_select select9-1.$iOuterLoop.16 {
1.240 + SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2
1.241 + } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
1.242 +
1.243 + test_compound_select select9-1.$iOuterLoop.17 {
1.244 + SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 1
1.245 + } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
1.246 +
1.247 + test_compound_select select9-1.$iOuterLoop.18 {
1.248 + SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 2
1.249 + } {8 eight 5 five 4 four 1 one 7 seven 10 ten 2 two}
1.250 +
1.251 + test_compound_select select9-1.$iOuterLoop.19 {
1.252 + SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 1, 2
1.253 + } {1 one 2 two 4 four 5 five 7 seven 8 eight 10 ten}
1.254 +
1.255 + test_compound_select select9-1.$iOuterLoop.20 {
1.256 + SELECT a, b FROM t1 EXCEPT SELECT d, e FROM t2 ORDER BY 2, 1
1.257 + } {8 eight 5 five 4 four 1 one 7 seven 10 ten 2 two}
1.258 +
1.259 + incr iOuterLoop
1.260 +}
1.261 +
1.262 +do_test select9-2.0 {
1.263 + execsql {
1.264 + DROP INDEX i1;
1.265 + DROP INDEX i2;
1.266 + DROP INDEX i3;
1.267 + DROP INDEX i4;
1.268 + }
1.269 +} {}
1.270 +
1.271 +proc reverse {lhs rhs} {
1.272 + return [string compare $rhs $lhs]
1.273 +}
1.274 +db collate reverse reverse
1.275 +
1.276 +# This loop is similar to the previous one (test cases select9-1.*)
1.277 +# except that the simple select statements have WHERE clauses attached
1.278 +# to them. Sometimes the WHERE clause may be satisfied using the same
1.279 +# index used for ORDER BY, sometimes not.
1.280 +#
1.281 +set iOuterLoop 1
1.282 +foreach indexes [list {
1.283 + /* Do not create any indexes. */
1.284 +} {
1.285 + CREATE INDEX i1 ON t1(a)
1.286 +} {
1.287 + DROP INDEX i1;
1.288 + CREATE INDEX i1 ON t1(b, a)
1.289 +} {
1.290 + CREATE INDEX i2 ON t2(d DESC, e COLLATE REVERSE ASC);
1.291 +} {
1.292 + CREATE INDEX i3 ON t1(a DESC);
1.293 +}] {
1.294 + do_test select9-2.$iOuterLoop.1 {
1.295 + execsql $indexes
1.296 + } {}
1.297 +
1.298 + test_compound_select_flippable select9-2.$iOuterLoop.2 {
1.299 + SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5 ORDER BY 1
1.300 + } {1 one I 2 two II 3 {} {} 4 four IV 5 ten XX 6 {} {} 7 fourteen XXVIII 8 sixteen XXXII 9 {} {} 10 twenty XL}
1.301 +
1.302 + test_compound_select_flippable select9-2.$iOuterLoop.2 {
1.303 + SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5 ORDER BY 2, 1
1.304 + } {3 {} {} 6 {} {} 9 {} {} 4 four IV 7 fourteen XXVIII 1 one I 8 sixteen XXXII 5 ten XX 10 twenty XL 2 two II}
1.305 +
1.306 + test_compound_select_flippable select9-2.$iOuterLoop.3 {
1.307 + SELECT * FROM t1 WHERE a<5 UNION SELECT * FROM t2 WHERE d>=5
1.308 + ORDER BY 2 COLLATE reverse, 1
1.309 + } {3 {} {} 6 {} {} 9 {} {} 2 two II 10 twenty XL 5 ten XX 8 sixteen XXXII 1 one I 7 fourteen XXVIII 4 four IV}
1.310 +
1.311 + test_compound_select_flippable select9-2.$iOuterLoop.4 {
1.312 + SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5 ORDER BY 1
1.313 + } {1 one I 2 two II 3 {} {} 4 four IV 5 ten XX 6 {} {} 7 fourteen XXVIII 8 sixteen XXXII 9 {} {} 10 twenty XL}
1.314 +
1.315 + test_compound_select_flippable select9-2.$iOuterLoop.5 {
1.316 + SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5 ORDER BY 2, 1
1.317 + } {3 {} {} 6 {} {} 9 {} {} 4 four IV 7 fourteen XXVIII 1 one I 8 sixteen XXXII 5 ten XX 10 twenty XL 2 two II}
1.318 +
1.319 + test_compound_select_flippable select9-2.$iOuterLoop.6 {
1.320 + SELECT * FROM t1 WHERE a<5 UNION ALL SELECT * FROM t2 WHERE d>=5
1.321 + ORDER BY 2 COLLATE reverse, 1
1.322 + } {3 {} {} 6 {} {} 9 {} {} 2 two II 10 twenty XL 5 ten XX 8 sixteen XXXII 1 one I 7 fourteen XXVIII 4 four IV}
1.323 +
1.324 + test_compound_select select9-2.$iOuterLoop.4 {
1.325 + SELECT a FROM t1 WHERE a<8 EXCEPT SELECT d FROM t2 WHERE d<=3 ORDER BY 1
1.326 + } {4 5 6 7}
1.327 +
1.328 + test_compound_select select9-2.$iOuterLoop.4 {
1.329 + SELECT a FROM t1 WHERE a<8 INTERSECT SELECT d FROM t2 WHERE d<=3 ORDER BY 1
1.330 + } {1 2 3}
1.331 +
1.332 +}
1.333 +
1.334 +do_test select9-2.X {
1.335 + execsql {
1.336 + DROP INDEX i1;
1.337 + DROP INDEX i2;
1.338 + DROP INDEX i3;
1.339 + }
1.340 +} {}
1.341 +
1.342 +# This procedure executes the SQL. Then it checks the generated program
1.343 +# for the SQL and appends a "nosort" to the result if the program contains the
1.344 +# SortCallback opcode. If the program does not contain the SortCallback
1.345 +# opcode it appends "sort"
1.346 +#
1.347 +proc cksort {sql} {
1.348 + set ::sqlite_sort_count 0
1.349 + set data [execsql $sql]
1.350 + if {$::sqlite_sort_count} {set x sort} {set x nosort}
1.351 + lappend data $x
1.352 + return $data
1.353 +}
1.354 +
1.355 +# If the right indexes exist, the following query:
1.356 +#
1.357 +# SELECT t1.a FROM t1 UNION ALL SELECT t2.d FROM t2 ORDER BY 1
1.358 +#
1.359 +# can use indexes to run without doing a in-memory sort operation.
1.360 +# This block of tests (select9-3.*) is used to check if the same
1.361 +# is possible with:
1.362 +#
1.363 +# CREATE VIEW v1 AS SELECT a FROM t1 UNION ALL SELECT d FROM t2
1.364 +# SELECT a FROM v1 ORDER BY 1
1.365 +#
1.366 +# It turns out that it is.
1.367 +#
1.368 +do_test select9-3.1 {
1.369 + cksort { SELECT a FROM t1 ORDER BY 1 }
1.370 +} {1 2 3 4 5 6 7 8 9 10 sort}
1.371 +do_test select9-3.2 {
1.372 + execsql { CREATE INDEX i1 ON t1(a) }
1.373 + cksort { SELECT a FROM t1 ORDER BY 1 }
1.374 +} {1 2 3 4 5 6 7 8 9 10 nosort}
1.375 +do_test select9-3.3 {
1.376 + cksort { SELECT a FROM t1 UNION ALL SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
1.377 +} {1 1 2 2 3 sort}
1.378 +do_test select9-3.4 {
1.379 + execsql { CREATE INDEX i2 ON t2(d) }
1.380 + cksort { SELECT a FROM t1 UNION ALL SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
1.381 +} {1 1 2 2 3 nosort}
1.382 +do_test select9-3.5 {
1.383 + execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION ALL SELECT d FROM t2 }
1.384 + cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 }
1.385 +} {1 1 2 2 3 nosort}
1.386 +do_test select9-3.X {
1.387 + execsql {
1.388 + DROP INDEX i1;
1.389 + DROP INDEX i2;
1.390 + DROP VIEW v1;
1.391 + }
1.392 +} {}
1.393 +
1.394 +# This block of tests is the same as the preceding one, except that
1.395 +# "UNION" is tested instead of "UNION ALL".
1.396 +#
1.397 +do_test select9-4.1 {
1.398 + cksort { SELECT a FROM t1 ORDER BY 1 }
1.399 +} {1 2 3 4 5 6 7 8 9 10 sort}
1.400 +do_test select9-4.2 {
1.401 + execsql { CREATE INDEX i1 ON t1(a) }
1.402 + cksort { SELECT a FROM t1 ORDER BY 1 }
1.403 +} {1 2 3 4 5 6 7 8 9 10 nosort}
1.404 +do_test select9-4.3 {
1.405 + cksort { SELECT a FROM t1 UNION SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
1.406 +} {1 2 3 4 5 sort}
1.407 +do_test select9-4.4 {
1.408 + execsql { CREATE INDEX i2 ON t2(d) }
1.409 + cksort { SELECT a FROM t1 UNION SELECT d FROM t2 ORDER BY 1 LIMIT 5 }
1.410 +} {1 2 3 4 5 nosort}
1.411 +do_test select9-4.5 {
1.412 + execsql { CREATE VIEW v1 AS SELECT a FROM t1 UNION SELECT d FROM t2 }
1.413 + cksort { SELECT a FROM v1 ORDER BY 1 LIMIT 5 }
1.414 +} {1 2 3 4 5 sort}
1.415 +do_test select9-4.X {
1.416 + execsql {
1.417 + DROP INDEX i1;
1.418 + DROP INDEX i2;
1.419 + DROP VIEW v1;
1.420 + }
1.421 +} {}
1.422 +
1.423 +
1.424 +finish_test