postgres-framework 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7de4fce4ee1d766622a91386c69d99c36ab1887a
4
+ data.tar.gz: ba02abcb768f9df778f38e88fe6dee57175a9ba7
5
+ SHA512:
6
+ metadata.gz: 945530251d2eb217ee12976ebb6526f9178ed1fcbb67aa10895ac809acb67cc9335f33ce553a1102156cdf5ab0a5b60ed7a1985cb03f0a85f46ba2bc3cfa082b
7
+ data.tar.gz: b2f06ea23ce47fbfdd0cc74299a623979ec3e1f514ba3fa0fef9fc09b5b8d07b9e88d911f569f76342652c728457805d78e206029400d8e5a8a817f29ed956aa
@@ -0,0 +1,3 @@
1
+ [submodule "plpgunit"]
2
+ path = plpgunit
3
+ url = https://github.com/mikoweb/plpgunit
data/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ Copyright (c) 2017, Rafał Mikołajun
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+ 3. All advertising materials mentioning features or use of this software
12
+ must display the following acknowledgement:
13
+ This product includes software developed by the Rafał Mikołajun.
14
+ 4. The name Rafał Mikołajun may not be used to endorse or promote products
15
+ derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY Rafał Mikołajun ''AS IS'' AND ANY
18
+ EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20
+ DISCLAIMED. IN NO EVENT SHALL Rafał Mikołajun BE LIABLE FOR ANY
21
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,50 @@
1
+ # PostgreSQL Framework
2
+
3
+ It's simple framework for PostgreSQL database with unit tests and versioning.
4
+
5
+ Created based on:
6
+
7
+ - [PostgreSQL Unit Testing Framework](https://github.com/mixerp/plpgunit)
8
+ - [Schema Evolution Manager (sem)](https://github.com/mbryzek/schema-evolution-manager)
9
+
10
+ ## Install framework
11
+
12
+ bundle install
13
+
14
+ ## Add framework scripts
15
+
16
+ bundle exec sem-add-framework
17
+
18
+ ## Add script
19
+
20
+ bundle exec sem-add-safe ./new-script.sql
21
+
22
+ ## Applying changes to your local database
23
+
24
+ bundle exec sem-apply --url postgresql://postgres@localhost/sample --password
25
+
26
+ ## Other commands
27
+
28
+ Go to [Schema Evolution Manager (sem)](https://github.com/mbryzek/schema-evolution-manager).
29
+
30
+ ## Run tests
31
+
32
+ BEGIN TRANSACTION;
33
+ SELECT * FROM unit_tests.begin();
34
+ ROLLBACK TRANSACTION;
35
+
36
+ ## Writing tests
37
+
38
+ Go to [PostgreSQL Unit Testing Framework](https://github.com/mixerp/plpgunit).
39
+
40
+ ## Uninstall tests
41
+
42
+ DROP SCHEMA IF EXISTS assert CASCADE;
43
+ DROP SCHEMA IF EXISTS unit_tests CASCADE;
44
+ DROP DOMAIN IF EXISTS public.test_result CASCADE;
45
+
46
+ ## Uninstall framework
47
+
48
+ It's' dangerous.
49
+
50
+ DROP SCHEMA IF EXISTS framework CASCADE;
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # == Adds a database framework scripts to this repository.
3
+ #
4
+ # == Usage
5
+ # sem-add-framework
6
+ #
7
+ # == Example
8
+ # sem-add-framework
9
+ #
10
+
11
+ require 'schema-evolution-manager'
12
+ require 'fileutils'
13
+
14
+ scripts_dir = File.join(`pwd`.strip, 'scripts')
15
+ SchemaEvolutionManager::Library.ensure_dir!(scripts_dir)
16
+
17
+ Dir.glob(File.dirname(__FILE__) + '/../scripts/*.sql') do |file|
18
+ target = File.join(scripts_dir, File.basename(file))
19
+ FileUtils.cp(file, target)
20
+ SchemaEvolutionManager::Library.system_or_error("git add #{target}")
21
+ end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+ # == Adds a database upgrade script to this repository.
3
+ #
4
+ # == Usage
5
+ # sem-add-safe <path>
6
+ #
7
+ # == Example
8
+ # sem-add-safe ./new-script.sql
9
+ #
10
+
11
+ require 'schema-evolution-manager'
12
+ require 'tempfile'
13
+
14
+ file = ARGV.shift
15
+ if file.to_s.strip.length == 0
16
+ puts '**** ERROR: Need file path'
17
+ SchemaEvolutionManager::RdocUsage.printAndExit(1)
18
+ end
19
+
20
+ SchemaEvolutionManager::Preconditions.check_state(File.exists?(file), "File[#{file}] could not be found")
21
+ SchemaEvolutionManager::Preconditions.check_state(file.match(/\.sql/i), "File[#{file}] must end with .sql")
22
+
23
+ tmp_file = Tempfile.new(File.basename(file))
24
+ begin
25
+ tmp_file.write(IO.read(file))
26
+ ensure
27
+ tmp_file.close
28
+ Bundler.with_clean_env do
29
+ `sem-add #{tmp_file.path}`
30
+ end
31
+ tmp_file.unlink
32
+ end
@@ -0,0 +1 @@
1
+ placeholder
@@ -0,0 +1,26 @@
1
+
2
+ /********************************************************************************
3
+ The PostgreSQL License
4
+
5
+ Copyright (c) 2014, Binod Nepal, Mix Open Foundation (http://mixof.org).
6
+
7
+ Permission to use, copy, modify, and distribute this software and its documentation
8
+ for any purpose, without fee, and without a written agreement is hereby granted,
9
+ provided that the above copyright notice and this paragraph and
10
+ the following two paragraphs appear in all copies.
11
+
12
+ IN NO EVENT SHALL MIX OPEN FOUNDATION BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
13
+ SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
14
+ ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
15
+ MIX OPEN FOUNDATION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
16
+
17
+ MIX OPEN FOUNDATION SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
18
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19
+ FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
20
+ AND MIX OPEN FOUNDATION HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
21
+ UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
22
+ ***********************************************************************************/
23
+
24
+ DROP SCHEMA IF EXISTS assert CASCADE;
25
+ DROP SCHEMA IF EXISTS unit_tests CASCADE;
26
+ DROP DOMAIN IF EXISTS public.test_result CASCADE;
@@ -0,0 +1,972 @@
1
+ /********************************************************************************
2
+ The PostgreSQL License
3
+
4
+ Copyright (c) 2014, Binod Nepal, Mix Open Foundation (http://mixof.org).
5
+
6
+ Permission to use, copy, modify, and distribute this software and its documentation
7
+ for any purpose, without fee, and without a written agreement is hereby granted,
8
+ provided that the above copyright notice and this paragraph and
9
+ the following two paragraphs appear in all copies.
10
+
11
+ IN NO EVENT SHALL MIX OPEN FOUNDATION BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
12
+ SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
13
+ ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
14
+ MIX OPEN FOUNDATION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
+
16
+ MIX OPEN FOUNDATION SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
17
+ BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
18
+ FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS,
19
+ AND MIX OPEN FOUNDATION HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
20
+ UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
21
+ ***********************************************************************************/
22
+
23
+ CREATE SCHEMA IF NOT EXISTS assert;
24
+ CREATE SCHEMA IF NOT EXISTS unit_tests;
25
+
26
+ DO
27
+ $$
28
+ BEGIN
29
+ IF NOT EXISTS
30
+ (
31
+ SELECT * FROM pg_type
32
+ WHERE
33
+ typname ='test_result'
34
+ AND
35
+ typnamespace =
36
+ (
37
+ SELECT oid FROM pg_namespace
38
+ WHERE nspname ='public'
39
+ )
40
+ ) THEN
41
+ CREATE DOMAIN public.test_result AS text;
42
+ END IF;
43
+ END
44
+ $$
45
+ LANGUAGE plpgsql;
46
+
47
+
48
+ DROP TABLE IF EXISTS unit_tests.test_details CASCADE;
49
+ DROP TABLE IF EXISTS unit_tests.tests CASCADE;
50
+ DROP TABLE IF EXISTS unit_tests.dependencies CASCADE;
51
+ CREATE TABLE unit_tests.tests
52
+ (
53
+ test_id SERIAL NOT NULL PRIMARY KEY,
54
+ started_on TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
55
+ completed_on TIMESTAMP WITHOUT TIME ZONE NULL,
56
+ total_tests integer NULL DEFAULT(0),
57
+ failed_tests integer NULL DEFAULT(0),
58
+ skipped_tests integer NULL DEFAULT(0)
59
+ );
60
+
61
+ CREATE INDEX unit_tests_tests_started_on_inx
62
+ ON unit_tests.tests(started_on);
63
+
64
+ CREATE INDEX unit_tests_tests_completed_on_inx
65
+ ON unit_tests.tests(completed_on);
66
+
67
+ CREATE INDEX unit_tests_tests_failed_tests_inx
68
+ ON unit_tests.tests(failed_tests);
69
+
70
+ CREATE TABLE unit_tests.test_details
71
+ (
72
+ id BIGSERIAL NOT NULL PRIMARY KEY,
73
+ test_id integer NOT NULL REFERENCES unit_tests.tests(test_id),
74
+ function_name text NOT NULL,
75
+ message text NOT NULL,
76
+ ts TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT(CURRENT_TIMESTAMP AT TIME ZONE 'UTC'),
77
+ status boolean NOT NULL,
78
+ executed boolean NOT NULL
79
+ );
80
+
81
+ CREATE INDEX unit_tests_test_details_test_id_inx
82
+ ON unit_tests.test_details(test_id);
83
+
84
+ CREATE INDEX unit_tests_test_details_status_inx
85
+ ON unit_tests.test_details(status);
86
+
87
+ CREATE TABLE unit_tests.dependencies
88
+ (
89
+ dependency_id BIGSERIAL NOT NULL PRIMARY KEY,
90
+ dependent_ns text,
91
+ dependent_function_name text NOT NULL,
92
+ depends_on_ns text,
93
+ depends_on_function_name text NOT NULL
94
+ );
95
+
96
+ CREATE INDEX unit_tests_dependencies_dependency_id_inx
97
+ ON unit_tests.dependencies(dependency_id);
98
+
99
+
100
+ DROP FUNCTION IF EXISTS assert.fail(message text);
101
+ CREATE FUNCTION assert.fail(message text)
102
+ RETURNS text
103
+ AS
104
+ $$
105
+ BEGIN
106
+ IF $1 IS NULL OR trim($1) = '' THEN
107
+ message := 'NO REASON SPECIFIED';
108
+ END IF;
109
+
110
+ RAISE WARNING 'ASSERT FAILED : %', message;
111
+ RETURN message;
112
+ END
113
+ $$
114
+ LANGUAGE plpgsql
115
+ IMMUTABLE STRICT;
116
+
117
+ DROP FUNCTION IF EXISTS assert.pass(message text);
118
+ CREATE FUNCTION assert.pass(message text)
119
+ RETURNS text
120
+ AS
121
+ $$
122
+ BEGIN
123
+ RAISE NOTICE 'ASSERT PASSED : %', message;
124
+ RETURN '';
125
+ END
126
+ $$
127
+ LANGUAGE plpgsql
128
+ IMMUTABLE STRICT;
129
+
130
+ DROP FUNCTION IF EXISTS assert.ok(message text);
131
+ CREATE FUNCTION assert.ok(message text)
132
+ RETURNS text
133
+ AS
134
+ $$
135
+ BEGIN
136
+ RAISE NOTICE 'OK : %', message;
137
+ RETURN '';
138
+ END
139
+ $$
140
+ LANGUAGE plpgsql
141
+ IMMUTABLE STRICT;
142
+
143
+ DROP FUNCTION IF EXISTS assert.is_equal(IN have anyelement, IN want anyelement, OUT message text, OUT result boolean);
144
+ CREATE FUNCTION assert.is_equal(IN have anyelement, IN want anyelement, OUT message text, OUT result boolean)
145
+ AS
146
+ $$
147
+ BEGIN
148
+ IF($1 IS NOT DISTINCT FROM $2) THEN
149
+ message := 'Assert is equal.';
150
+ PERFORM assert.ok(message);
151
+ result := true;
152
+ RETURN;
153
+ END IF;
154
+
155
+ message := E'ASSERT IS_EQUAL FAILED.\n\nHave -> ' || COALESCE($1::text, 'NULL') || E'\nWant -> ' || COALESCE($2::text, 'NULL') || E'\n';
156
+ PERFORM assert.fail(message);
157
+ result := false;
158
+ RETURN;
159
+ END
160
+ $$
161
+ LANGUAGE plpgsql
162
+ IMMUTABLE;
163
+
164
+
165
+ DROP FUNCTION IF EXISTS assert.are_equal(VARIADIC anyarray, OUT message text, OUT result boolean);
166
+ CREATE FUNCTION assert.are_equal(VARIADIC anyarray, OUT message text, OUT result boolean)
167
+ AS
168
+ $$
169
+ DECLARE count integer=0;
170
+ DECLARE total_items bigint;
171
+ DECLARE total_rows bigint;
172
+ BEGIN
173
+ result := false;
174
+
175
+ WITH counter
176
+ AS
177
+ (
178
+ SELECT *
179
+ FROM explode_array($1) AS items
180
+ )
181
+ SELECT
182
+ COUNT(items),
183
+ COUNT(*)
184
+ INTO
185
+ total_items,
186
+ total_rows
187
+ FROM counter;
188
+
189
+ IF(total_items = 0 OR total_items = total_rows) THEN
190
+ result := true;
191
+ END IF;
192
+
193
+ IF(result AND total_items > 0) THEN
194
+ SELECT COUNT(DISTINCT $1[s.i])
195
+ INTO count
196
+ FROM generate_series(array_lower($1,1), array_upper($1,1)) AS s(i)
197
+ ORDER BY 1;
198
+
199
+ IF count <> 1 THEN
200
+ result := FALSE;
201
+ END IF;
202
+ END IF;
203
+
204
+ IF(NOT result) THEN
205
+ message := 'ASSERT ARE_EQUAL FAILED.';
206
+ PERFORM assert.fail(message);
207
+ RETURN;
208
+ END IF;
209
+
210
+ message := 'Asserts are equal.';
211
+ PERFORM assert.ok(message);
212
+ result := true;
213
+ RETURN;
214
+ END
215
+ $$
216
+ LANGUAGE plpgsql
217
+ IMMUTABLE;
218
+
219
+ DROP FUNCTION IF EXISTS assert.is_not_equal(IN already_have anyelement, IN dont_want anyelement, OUT message text, OUT result boolean);
220
+ CREATE FUNCTION assert.is_not_equal(IN already_have anyelement, IN dont_want anyelement, OUT message text, OUT result boolean)
221
+ AS
222
+ $$
223
+ BEGIN
224
+ IF($1 IS DISTINCT FROM $2) THEN
225
+ message := 'Assert is not equal.';
226
+ PERFORM assert.ok(message);
227
+ result := true;
228
+ RETURN;
229
+ END IF;
230
+
231
+ message := E'ASSERT IS_NOT_EQUAL FAILED.\n\nAlready Have -> ' || COALESCE($1::text, 'NULL') || E'\nDon''t Want -> ' || COALESCE($2::text, 'NULL') || E'\n';
232
+ PERFORM assert.fail(message);
233
+ result := false;
234
+ RETURN;
235
+ END
236
+ $$
237
+ LANGUAGE plpgsql
238
+ IMMUTABLE;
239
+
240
+ DROP FUNCTION IF EXISTS assert.are_not_equal(VARIADIC anyarray, OUT message text, OUT result boolean);
241
+ CREATE FUNCTION assert.are_not_equal(VARIADIC anyarray, OUT message text, OUT result boolean)
242
+ AS
243
+ $$
244
+ DECLARE count integer=0;
245
+ DECLARE count_nulls bigint;
246
+ BEGIN
247
+ SELECT COUNT(*)
248
+ INTO count_nulls
249
+ FROM explode_array($1) AS items
250
+ WHERE items IS NULL;
251
+
252
+ SELECT COUNT(DISTINCT $1[s.i]) INTO count
253
+ FROM generate_series(array_lower($1,1), array_upper($1,1)) AS s(i)
254
+ ORDER BY 1;
255
+
256
+ IF(count + count_nulls <> array_upper($1,1) OR count_nulls > 1) THEN
257
+ message := 'ASSERT ARE_NOT_EQUAL FAILED.';
258
+ PERFORM assert.fail(message);
259
+ RESULT := FALSE;
260
+ RETURN;
261
+ END IF;
262
+
263
+ message := 'Asserts are not equal.';
264
+ PERFORM assert.ok(message);
265
+ result := true;
266
+ RETURN;
267
+ END
268
+ $$
269
+ LANGUAGE plpgsql
270
+ IMMUTABLE;
271
+
272
+
273
+ DROP FUNCTION IF EXISTS assert.is_null(IN anyelement, OUT message text, OUT result boolean);
274
+ CREATE FUNCTION assert.is_null(IN anyelement, OUT message text, OUT result boolean)
275
+ AS
276
+ $$
277
+ BEGIN
278
+ IF($1 IS NULL) THEN
279
+ message := 'Assert is NULL.';
280
+ PERFORM assert.ok(message);
281
+ result := true;
282
+ RETURN;
283
+ END IF;
284
+
285
+ message := E'ASSERT IS_NULL FAILED. NULL value was expected.\n\n\n';
286
+ PERFORM assert.fail(message);
287
+ result := false;
288
+ RETURN;
289
+ END
290
+ $$
291
+ LANGUAGE plpgsql
292
+ IMMUTABLE;
293
+
294
+ DROP FUNCTION IF EXISTS assert.is_not_null(IN anyelement, OUT message text, OUT result boolean);
295
+ CREATE FUNCTION assert.is_not_null(IN anyelement, OUT message text, OUT result boolean)
296
+ AS
297
+ $$
298
+ BEGIN
299
+ IF($1 IS NOT NULL) THEN
300
+ message := 'Assert is not NULL.';
301
+ PERFORM assert.ok(message);
302
+ result := true;
303
+ RETURN;
304
+ END IF;
305
+
306
+ message := E'ASSERT IS_NOT_NULL FAILED. The value is NULL.\n\n\n';
307
+ PERFORM assert.fail(message);
308
+ result := false;
309
+ RETURN;
310
+ END
311
+ $$
312
+ LANGUAGE plpgsql
313
+ IMMUTABLE;
314
+
315
+ DROP FUNCTION IF EXISTS assert.is_true(IN boolean, OUT message text, OUT result boolean);
316
+ CREATE FUNCTION assert.is_true(IN boolean, OUT message text, OUT result boolean)
317
+ AS
318
+ $$
319
+ BEGIN
320
+ IF($1) THEN
321
+ message := 'Assert is true.';
322
+ PERFORM assert.ok(message);
323
+ result := true;
324
+ RETURN;
325
+ END IF;
326
+
327
+ message := E'ASSERT IS_TRUE FAILED. A true condition was expected.\n\n\n';
328
+ PERFORM assert.fail(message);
329
+ result := false;
330
+ RETURN;
331
+ END
332
+ $$
333
+ LANGUAGE plpgsql
334
+ IMMUTABLE;
335
+
336
+ DROP FUNCTION IF EXISTS assert.is_false(IN boolean, OUT message text, OUT result boolean);
337
+ CREATE FUNCTION assert.is_false(IN boolean, OUT message text, OUT result boolean)
338
+ AS
339
+ $$
340
+ BEGIN
341
+ IF(NOT $1) THEN
342
+ message := 'Assert is false.';
343
+ PERFORM assert.ok(message);
344
+ result := true;
345
+ RETURN;
346
+ END IF;
347
+
348
+ message := E'ASSERT IS_FALSE FAILED. A false condition was expected.\n\n\n';
349
+ PERFORM assert.fail(message);
350
+ result := false;
351
+ RETURN;
352
+ END
353
+ $$
354
+ LANGUAGE plpgsql
355
+ IMMUTABLE;
356
+
357
+ DROP FUNCTION IF EXISTS assert.is_greater_than(IN x anyelement, IN y anyelement, OUT message text, OUT result boolean);
358
+ CREATE FUNCTION assert.is_greater_than(IN x anyelement, IN y anyelement, OUT message text, OUT result boolean)
359
+ AS
360
+ $$
361
+ BEGIN
362
+ IF($1 > $2) THEN
363
+ message := 'Assert greater than condition is satisfied.';
364
+ PERFORM assert.ok(message);
365
+ result := true;
366
+ RETURN;
367
+ END IF;
368
+
369
+ message := E'ASSERT IS_GREATER_THAN FAILED.\n\n X : -> ' || COALESCE($1::text, 'NULL') || E'\n is not greater than Y: -> ' || COALESCE($2::text, 'NULL') || E'\n';
370
+ PERFORM assert.fail(message);
371
+ result := false;
372
+ RETURN;
373
+ END
374
+ $$
375
+ LANGUAGE plpgsql
376
+ IMMUTABLE;
377
+
378
+ DROP FUNCTION IF EXISTS assert.is_less_than(IN x anyelement, IN y anyelement, OUT message text, OUT result boolean);
379
+ CREATE FUNCTION assert.is_less_than(IN x anyelement, IN y anyelement, OUT message text, OUT result boolean)
380
+ AS
381
+ $$
382
+ BEGIN
383
+ IF($1 < $2) THEN
384
+ message := 'Assert less than condition is satisfied.';
385
+ PERFORM assert.ok(message);
386
+ result := true;
387
+ RETURN;
388
+ END IF;
389
+
390
+ message := E'ASSERT IS_LESS_THAN FAILED.\n\n X : -> ' || COALESCE($1::text, 'NULL') || E'\n is not less than Y: -> ' || COALESCE($2::text, 'NULL') || E'\n';
391
+ PERFORM assert.fail(message);
392
+ result := false;
393
+ RETURN;
394
+ END
395
+ $$
396
+ LANGUAGE plpgsql
397
+ IMMUTABLE;
398
+
399
+ DROP FUNCTION IF EXISTS assert.function_exists(function_name text, OUT message text, OUT result boolean);
400
+ CREATE FUNCTION assert.function_exists(function_name text, OUT message text, OUT result boolean)
401
+ AS
402
+ $$
403
+ BEGIN
404
+ IF NOT EXISTS
405
+ (
406
+ SELECT 1
407
+ FROM pg_catalog.pg_namespace n
408
+ JOIN pg_catalog.pg_proc p
409
+ ON pronamespace = n.oid
410
+ WHERE replace(nspname || '.' || proname || '(' || oidvectortypes(proargtypes) || ')', ' ' , '')::text=$1
411
+ ) THEN
412
+ message := format('The function %s does not exist.', $1);
413
+ PERFORM assert.fail(message);
414
+
415
+ result := false;
416
+ RETURN;
417
+ END IF;
418
+
419
+ message := format('Ok. The function %s exists.', $1);
420
+ PERFORM assert.ok(message);
421
+ result := true;
422
+ RETURN;
423
+ END
424
+ $$
425
+ LANGUAGE plpgsql;
426
+
427
+ DROP FUNCTION IF EXISTS assert.if_functions_compile(VARIADIC _schema_name text[], OUT message text, OUT result boolean);
428
+ CREATE OR REPLACE FUNCTION assert.if_functions_compile
429
+ (
430
+ VARIADIC _schema_name text[],
431
+ OUT message text,
432
+ OUT result boolean
433
+ )
434
+ AS
435
+ $$
436
+ DECLARE all_parameters text;
437
+ DECLARE current_function RECORD;
438
+ DECLARE current_function_name text;
439
+ DECLARE current_type text;
440
+ DECLARE current_type_schema text;
441
+ DECLARE current_parameter text;
442
+ DECLARE functions_count smallint := 0;
443
+ DECLARE current_parameters_count int;
444
+ DECLARE i int;
445
+ DECLARE command_text text;
446
+ DECLARE failed_functions text;
447
+ BEGIN
448
+ FOR current_function IN
449
+ SELECT proname, proargtypes, nspname
450
+ FROM pg_proc
451
+ INNER JOIN pg_namespace
452
+ ON pg_proc.pronamespace = pg_namespace.oid
453
+ WHERE pronamespace IN
454
+ (
455
+ SELECT oid FROM pg_namespace
456
+ WHERE nspname = ANY($1)
457
+ AND nspname NOT IN
458
+ (
459
+ 'assert', 'unit_tests', 'information_schema'
460
+ )
461
+ AND proname NOT IN('if_functions_compile')
462
+ )
463
+ LOOP
464
+ current_parameters_count := array_upper(current_function.proargtypes, 1) + 1;
465
+
466
+ i := 0;
467
+ all_parameters := '';
468
+
469
+ LOOP
470
+ IF i < current_parameters_count THEN
471
+ IF i > 0 THEN
472
+ all_parameters := all_parameters || ', ';
473
+ END IF;
474
+
475
+ SELECT
476
+ nspname, typname
477
+ INTO
478
+ current_type_schema, current_type
479
+ FROM pg_type
480
+ INNER JOIN pg_namespace
481
+ ON pg_type.typnamespace = pg_namespace.oid
482
+ WHERE pg_type.oid = current_function.proargtypes[i];
483
+
484
+ IF(current_type IN('int4', 'int8', 'numeric', 'integer_strict', 'money_strict','decimal_strict', 'integer_strict2', 'money_strict2','decimal_strict2', 'money','decimal', 'numeric', 'bigint')) THEN
485
+ current_parameter := '1::' || current_type_schema || '.' || current_type;
486
+ ELSIF(substring(current_type, 1, 1) = '_') THEN
487
+ current_parameter := 'NULL::' || current_type_schema || '.' || substring(current_type, 2, length(current_type)) || '[]';
488
+ ELSIF(current_type in ('date')) THEN
489
+ current_parameter := '''1-1-2000''::' || current_type;
490
+ ELSIF(current_type = 'bool') THEN
491
+ current_parameter := 'false';
492
+ ELSE
493
+ current_parameter := '''''::' || quote_ident(current_type_schema) || '.' || quote_ident(current_type);
494
+ END IF;
495
+
496
+ all_parameters = all_parameters || current_parameter;
497
+
498
+ i := i + 1;
499
+ ELSE
500
+ EXIT;
501
+ END IF;
502
+ END LOOP;
503
+
504
+ BEGIN
505
+ current_function_name := quote_ident(current_function.nspname) || '.' || quote_ident(current_function.proname);
506
+ command_text := 'SELECT * FROM ' || current_function_name || '(' || all_parameters || ');';
507
+
508
+ EXECUTE command_text;
509
+ functions_count := functions_count + 1;
510
+
511
+ EXCEPTION WHEN OTHERS THEN
512
+ IF(failed_functions IS NULL) THEN
513
+ failed_functions := '';
514
+ END IF;
515
+
516
+ IF(SQLSTATE IN('42702', '42704')) THEN
517
+ failed_functions := failed_functions || E'\n' || command_text || E'\n' || SQLERRM || E'\n';
518
+ END IF;
519
+ END;
520
+
521
+
522
+ END LOOP;
523
+
524
+ IF(failed_functions != '') THEN
525
+ message := E'The test if_functions_compile failed. The following functions failed to compile : \n\n' || failed_functions;
526
+ result := false;
527
+ PERFORM assert.fail(message);
528
+ RETURN;
529
+ END IF;
530
+ END;
531
+ $$
532
+ LANGUAGE plpgsql
533
+ VOLATILE;
534
+
535
+ DROP FUNCTION IF EXISTS assert.if_views_compile(VARIADIC _schema_name text[], OUT message text, OUT result boolean);
536
+ CREATE FUNCTION assert.if_views_compile
537
+ (
538
+ VARIADIC _schema_name text[],
539
+ OUT message text,
540
+ OUT result boolean
541
+ )
542
+ AS
543
+ $$
544
+
545
+ DECLARE message test_result;
546
+ DECLARE current_view RECORD;
547
+ DECLARE current_view_name text;
548
+ DECLARE command_text text;
549
+ DECLARE failed_views text;
550
+ BEGIN
551
+ FOR current_view IN
552
+ SELECT table_name, table_schema
553
+ FROM information_schema.views
554
+ WHERE table_schema = ANY($1)
555
+ LOOP
556
+
557
+ BEGIN
558
+ current_view_name := quote_ident(current_view.table_schema) || '.' || quote_ident(current_view.table_name);
559
+ command_text := 'SELECT * FROM ' || current_view_name || ' LIMIT 1;';
560
+
561
+ RAISE NOTICE '%', command_text;
562
+
563
+ EXECUTE command_text;
564
+
565
+ EXCEPTION WHEN OTHERS THEN
566
+ IF(failed_views IS NULL) THEN
567
+ failed_views := '';
568
+ END IF;
569
+
570
+ failed_views := failed_views || E'\n' || command_text || E'\n' || SQLERRM || E'\n';
571
+ END;
572
+
573
+
574
+ END LOOP;
575
+
576
+ IF(failed_views != '') THEN
577
+ message := E'The test if_views_compile failed. The following views failed to compile : \n\n' || failed_views;
578
+ result := false;
579
+ PERFORM assert.fail(message);
580
+ RETURN;
581
+ END IF;
582
+
583
+ RETURN;
584
+ END;
585
+ $$
586
+ LANGUAGE plpgsql
587
+ VOLATILE;
588
+
589
+
590
+ DROP FUNCTION IF EXISTS unit_tests.add_dependency(p_dependent text, p_depends_on text);
591
+ CREATE FUNCTION unit_tests.add_dependency(p_dependent text, p_depends_on text)
592
+ RETURNS void
593
+ AS
594
+ $$
595
+ DECLARE dependent_ns text;
596
+ DECLARE dependent_name text;
597
+ DECLARE depends_on_ns text;
598
+ DECLARE depends_on_name text;
599
+ DECLARE arr text[];
600
+ BEGIN
601
+ IF p_dependent LIKE '%.%' THEN
602
+ SELECT regexp_split_to_array(p_dependent, E'\\.') INTO arr;
603
+ SELECT arr[1] INTO dependent_ns;
604
+ SELECT arr[2] INTO dependent_name;
605
+ ELSE
606
+ SELECT NULL INTO dependent_ns;
607
+ SELECT p_dependent INTO dependent_name;
608
+ END IF;
609
+ IF p_depends_on LIKE '%.%' THEN
610
+ SELECT regexp_split_to_array(p_depends_on, E'\\.') INTO arr;
611
+ SELECT arr[1] INTO depends_on_ns;
612
+ SELECT arr[2] INTO depends_on_name;
613
+ ELSE
614
+ SELECT NULL INTO depends_on_ns;
615
+ SELECT p_depends_on INTO depends_on_name;
616
+ END IF;
617
+ INSERT INTO unit_tests.dependencies (dependent_ns, dependent_function_name, depends_on_ns, depends_on_function_name)
618
+ VALUES (dependent_ns, dependent_name, depends_on_ns, depends_on_name);
619
+ END
620
+ $$
621
+ LANGUAGE plpgsql
622
+ STRICT;
623
+
624
+
625
+ DROP FUNCTION IF EXISTS unit_tests.begin(verbosity integer, format text);
626
+ CREATE FUNCTION unit_tests.begin(verbosity integer DEFAULT 9, format text DEFAULT '')
627
+ RETURNS TABLE(message text, result character(1))
628
+ AS
629
+ $$
630
+ DECLARE this record;
631
+ DECLARE _function_name text;
632
+ DECLARE _sql text;
633
+ DECLARE _failed_dependencies text[];
634
+ DECLARE _num_of_test_functions integer;
635
+ DECLARE _should_skip boolean;
636
+ DECLARE _message text;
637
+ DECLARE _error text;
638
+ DECLARE _context text;
639
+ DECLARE _result character(1);
640
+ DECLARE _test_id integer;
641
+ DECLARE _status boolean;
642
+ DECLARE _total_tests integer = 0;
643
+ DECLARE _failed_tests integer = 0;
644
+ DECLARE _skipped_tests integer = 0;
645
+ DECLARE _list_of_failed_tests text;
646
+ DECLARE _list_of_skipped_tests text;
647
+ DECLARE _started_from TIMESTAMP WITHOUT TIME ZONE;
648
+ DECLARE _completed_on TIMESTAMP WITHOUT TIME ZONE;
649
+ DECLARE _delta integer;
650
+ DECLARE _ret_val text = '';
651
+ DECLARE _verbosity text[] =
652
+ ARRAY['debug5', 'debug4', 'debug3', 'debug2', 'debug1', 'log', 'notice', 'warning', 'error', 'fatal', 'panic'];
653
+ BEGIN
654
+ _started_from := clock_timestamp() AT TIME ZONE 'UTC';
655
+
656
+ IF(format='teamcity') THEN
657
+ RAISE INFO '##teamcity[testSuiteStarted name=''Plpgunit'' message=''Test started from : %'']', _started_from;
658
+ ELSE
659
+ RAISE INFO 'Test started from : %', _started_from;
660
+ END IF;
661
+
662
+ IF($1 > 11) THEN
663
+ $1 := 9;
664
+ END IF;
665
+
666
+ EXECUTE 'SET CLIENT_MIN_MESSAGES TO ' || _verbosity[$1];
667
+ RAISE WARNING 'CLIENT_MIN_MESSAGES set to : %' , _verbosity[$1];
668
+
669
+ SELECT nextval('unit_tests.tests_test_id_seq') INTO _test_id;
670
+
671
+ INSERT INTO unit_tests.tests(test_id)
672
+ SELECT _test_id;
673
+
674
+ DROP TABLE IF EXISTS temp_test_functions;
675
+ CREATE TEMP TABLE temp_test_functions AS
676
+ SELECT
677
+ nspname AS ns_name,
678
+ proname AS function_name,
679
+ p.oid as oid
680
+ FROM pg_catalog.pg_namespace n
681
+ JOIN pg_catalog.pg_proc p
682
+ ON pronamespace = n.oid
683
+ WHERE
684
+ prorettype='test_result'::regtype::oid;
685
+
686
+ SELECT count(*) INTO _num_of_test_functions FROM temp_test_functions;
687
+
688
+ DROP TABLE IF EXISTS temp_dependency_levels;
689
+ CREATE TEMP TABLE temp_dependency_levels AS
690
+ WITH RECURSIVE dependency_levels(ns_name, function_name, oid, level) AS (
691
+ -- select functions without any dependencies
692
+ SELECT ns_name, function_name, tf.oid, 0 as level
693
+ FROM temp_test_functions tf
694
+ LEFT OUTER JOIN unit_tests.dependencies d ON tf.ns_name = d.dependent_ns AND tf.function_name = d.dependent_function_name
695
+ WHERE d.dependency_id IS NULL
696
+ UNION
697
+ -- add functions which depend on the previous level functions
698
+ SELECT d.dependent_ns, d.dependent_function_name, tf.oid, level + 1
699
+ FROM dependency_levels dl
700
+ JOIN unit_tests.dependencies d ON dl.ns_name = d.depends_on_ns AND dl.function_name LIKE d.depends_on_function_name
701
+ JOIN temp_test_functions tf ON d.dependent_ns = tf.ns_name AND d.dependent_function_name = tf.function_name
702
+ WHERE level < _num_of_test_functions -- don't follow circles for too long
703
+ )
704
+ SELECT ns_name, function_name, oid, max(level) as max_level
705
+ FROM dependency_levels
706
+ GROUP BY ns_name, function_name, oid;
707
+
708
+ IF (SELECT count(*) < _num_of_test_functions FROM temp_dependency_levels) THEN
709
+ SELECT array_to_string(array_agg(tf.ns_name || '.' || tf.function_name || '()'), ', ')
710
+ INTO _error
711
+ FROM temp_test_functions tf
712
+ LEFT OUTER JOIN temp_dependency_levels dl ON tf.oid = dl.oid
713
+ WHERE dl.oid IS NULL;
714
+ RAISE EXCEPTION 'Cyclic dependencies detected. Check the following test functions: %', _error;
715
+ END IF;
716
+
717
+ IF exists(SELECT * FROM temp_dependency_levels WHERE max_level = _num_of_test_functions) THEN
718
+ SELECT array_to_string(array_agg(ns_name || '.' || function_name || '()'), ', ')
719
+ INTO _error
720
+ FROM temp_dependency_levels
721
+ WHERE max_level = _num_of_test_functions;
722
+ RAISE EXCEPTION 'Cyclic dependencies detected. Check the dependency graph including following test functions: %', _error;
723
+ END IF;
724
+
725
+ FOR this IN
726
+ SELECT ns_name, function_name, max_level
727
+ FROM temp_dependency_levels
728
+ ORDER BY max_level, oid
729
+ LOOP
730
+ BEGIN
731
+ _status := false;
732
+ _total_tests := _total_tests + 1;
733
+
734
+ _function_name = this.ns_name|| '.' || this.function_name || '()';
735
+
736
+ SELECT array_agg(td.function_name)
737
+ INTO _failed_dependencies
738
+ FROM unit_tests.dependencies d
739
+ JOIN unit_tests.test_details td on td.function_name LIKE d.depends_on_ns || '.' || d.depends_on_function_name || '()'
740
+ WHERE d.dependent_ns = this.ns_name AND d.dependent_function_name = this.function_name
741
+ AND test_id = _test_id AND status = false;
742
+
743
+ SELECT _failed_dependencies IS NOT NULL INTO _should_skip;
744
+ IF NOT _should_skip THEN
745
+ _sql := 'SELECT ' || _function_name || ';';
746
+
747
+ RAISE NOTICE 'RUNNING TEST : %.', _function_name;
748
+
749
+ IF(format='teamcity') THEN
750
+ RAISE INFO '##teamcity[testStarted name=''%'' message=''%'']', _function_name, _started_from;
751
+ ELSE
752
+ RAISE INFO 'Running test % : %', _function_name, _started_from;
753
+ END IF;
754
+
755
+ EXECUTE _sql INTO _message;
756
+
757
+ IF _message = '' THEN
758
+ _status := true;
759
+
760
+ IF(format='teamcity') THEN
761
+ RAISE INFO '##teamcity[testFinished name=''%'' message=''%'']', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
762
+ ELSE
763
+ RAISE INFO 'Passed % : %', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
764
+ END IF;
765
+ ELSE
766
+ IF(format='teamcity') THEN
767
+ RAISE INFO '##teamcity[testFailed name=''%'' message=''%'']', _function_name, _message;
768
+ RAISE INFO '##teamcity[testFinished name=''%'' message=''%'']', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
769
+ ELSE
770
+ RAISE INFO 'Test failed % : %', _function_name, _message;
771
+ END IF;
772
+ END IF;
773
+ ELSE
774
+ -- skipped test
775
+ _status := true;
776
+ _message = 'Failed dependencies: ' || array_to_string(_failed_dependencies, ',');
777
+ IF(format='teamcity') THEN
778
+ RAISE INFO '##teamcity[testSkipped name=''%''] : %', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
779
+ ELSE
780
+ RAISE INFO 'Skipped % : %', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
781
+ END IF;
782
+ END IF;
783
+
784
+ INSERT INTO unit_tests.test_details(test_id, function_name, message, status, executed, ts)
785
+ SELECT _test_id, _function_name, _message, _status, NOT _should_skip, clock_timestamp();
786
+
787
+ IF NOT _status THEN
788
+ _failed_tests := _failed_tests + 1;
789
+ RAISE WARNING 'TEST % FAILED.', _function_name;
790
+ RAISE WARNING 'REASON: %', _message;
791
+ ELSIF NOT _should_skip THEN
792
+ RAISE NOTICE 'TEST % COMPLETED WITHOUT ERRORS.', _function_name;
793
+ ELSE
794
+ _skipped_tests := _skipped_tests + 1;
795
+ RAISE WARNING 'TEST % SKIPPED, BECAUSE A DEPENDENCY FAILED.', _function_name;
796
+ END IF;
797
+
798
+ EXCEPTION WHEN OTHERS THEN
799
+ GET STACKED DIAGNOSTICS _context = PG_EXCEPTION_CONTEXT;
800
+ _message := 'ERR: [' || SQLSTATE || ']: ' || SQLERRM || E'\n ' || split_part(_context, E'\n', 1);
801
+ INSERT INTO unit_tests.test_details(test_id, function_name, message, status, executed)
802
+ SELECT _test_id, _function_name, _message, false, true;
803
+
804
+ _failed_tests := _failed_tests + 1;
805
+
806
+ RAISE WARNING 'TEST % FAILED.', _function_name;
807
+ RAISE WARNING 'REASON: %', _message;
808
+
809
+ IF(format='teamcity') THEN
810
+ RAISE INFO '##teamcity[testFailed name=''%'' message=''%'']', _function_name, _message;
811
+ RAISE INFO '##teamcity[testFinished name=''%'' message=''%'']', _function_name, clock_timestamp() AT TIME ZONE 'UTC';
812
+ ELSE
813
+ RAISE INFO 'Test failed % : %', _function_name, _message;
814
+ END IF;
815
+ END;
816
+ END LOOP;
817
+
818
+ _completed_on := clock_timestamp() AT TIME ZONE 'UTC';
819
+ _delta := extract(millisecond from _completed_on - _started_from)::integer;
820
+
821
+ UPDATE unit_tests.tests
822
+ SET total_tests = _total_tests, failed_tests = _failed_tests, skipped_tests = _skipped_tests, completed_on = _completed_on
823
+ WHERE test_id = _test_id;
824
+
825
+ IF format='junit' THEN
826
+ SELECT
827
+ '<?xml version="1.0" encoding="UTF-8"?>'||
828
+ xmlelement
829
+ (
830
+ name testsuites,
831
+ xmlelement
832
+ (
833
+ name testsuite,
834
+ xmlattributes
835
+ (
836
+ 'plpgunit' AS name,
837
+ t.total_tests AS tests,
838
+ t.failed_tests AS failures,
839
+ 0 AS errors,
840
+ EXTRACT
841
+ (
842
+ EPOCH FROM t.completed_on - t.started_on
843
+ ) AS time
844
+ ),
845
+ xmlagg
846
+ (
847
+ xmlelement
848
+ (
849
+ name testcase,
850
+ xmlattributes
851
+ (
852
+ td.function_name
853
+ AS name,
854
+ EXTRACT
855
+ (
856
+ EPOCH FROM td.ts - t.started_on
857
+ ) AS time
858
+ ),
859
+ CASE
860
+ WHEN td.status=false
861
+ THEN
862
+ xmlelement
863
+ (
864
+ name failure,
865
+ td.message
866
+ )
867
+ END
868
+ )
869
+ )
870
+ )
871
+ ) INTO _ret_val
872
+ FROM unit_tests.test_details td, unit_tests.tests t
873
+ WHERE
874
+ t.test_id=_test_id
875
+ AND
876
+ td.test_id=t.test_id
877
+ GROUP BY t.test_id;
878
+ ELSE
879
+ WITH failed_tests AS
880
+ (
881
+ SELECT row_number() OVER (ORDER BY id) AS id,
882
+ unit_tests.test_details.function_name,
883
+ unit_tests.test_details.message
884
+ FROM unit_tests.test_details
885
+ WHERE test_id = _test_id
886
+ AND status= false
887
+ )
888
+ SELECT array_to_string(array_agg(f.id::text || '. ' || f.function_name || ' --> ' || f.message), E'\n') INTO _list_of_failed_tests
889
+ FROM failed_tests f;
890
+
891
+ WITH skipped_tests AS
892
+ (
893
+ SELECT row_number() OVER (ORDER BY id) AS id,
894
+ unit_tests.test_details.function_name,
895
+ unit_tests.test_details.message
896
+ FROM unit_tests.test_details
897
+ WHERE test_id = _test_id
898
+ AND executed = false
899
+ )
900
+ SELECT array_to_string(array_agg(s.id::text || '. ' || s.function_name || ' --> ' || s.message), E'\n') INTO _list_of_skipped_tests
901
+ FROM skipped_tests s;
902
+
903
+ _ret_val := _ret_val || 'Test completed on : ' || _completed_on::text || E' UTC. \nTotal test runtime: ' || _delta::text || E' ms.\n';
904
+ _ret_val := _ret_val || E'\nTotal tests run : ' || COALESCE(_total_tests, '0')::text;
905
+ _ret_val := _ret_val || E'.\nPassed tests : ' || (COALESCE(_total_tests, '0') - COALESCE(_failed_tests, '0') - COALESCE(_skipped_tests, '0'))::text;
906
+ _ret_val := _ret_val || E'.\nFailed tests : ' || COALESCE(_failed_tests, '0')::text;
907
+ _ret_val := _ret_val || E'.\nSkipped tests : ' || COALESCE(_skipped_tests, '0')::text;
908
+ _ret_val := _ret_val || E'.\n\nList of failed tests:\n' || '----------------------';
909
+ _ret_val := _ret_val || E'\n' || COALESCE(_list_of_failed_tests, '<NULL>')::text;
910
+ _ret_val := _ret_val || E'.\n\nList of skipped tests:\n' || '----------------------';
911
+ _ret_val := _ret_val || E'\n' || COALESCE(_list_of_skipped_tests, '<NULL>')::text;
912
+ _ret_val := _ret_val || E'\n' || E'End of plpgunit test.\n\n';
913
+ END IF;
914
+
915
+ IF _failed_tests > 0 THEN
916
+ _result := 'N';
917
+
918
+ IF(format='teamcity') THEN
919
+ RAISE INFO '##teamcity[testStarted name=''Result'']';
920
+ RAISE INFO '##teamcity[testFailed name=''Result'' message=''%'']', REPLACE(_ret_val, E'\n', ' |n');
921
+ RAISE INFO '##teamcity[testFinished name=''Result'']';
922
+ RAISE INFO '##teamcity[testSuiteFinished name=''Plpgunit'' message=''%'']', REPLACE(_ret_val, E'\n', '|n');
923
+ ELSE
924
+ RAISE INFO '%', _ret_val;
925
+ END IF;
926
+ ELSE
927
+ _result := 'Y';
928
+
929
+ IF(format='teamcity') THEN
930
+ RAISE INFO '##teamcity[testSuiteFinished name=''Plpgunit'' message=''%'']', REPLACE(_ret_val, E'\n', '|n');
931
+ ELSE
932
+ RAISE INFO '%', _ret_val;
933
+ END IF;
934
+ END IF;
935
+
936
+ SET CLIENT_MIN_MESSAGES TO notice;
937
+
938
+ RETURN QUERY SELECT _ret_val, _result;
939
+ END
940
+ $$
941
+ LANGUAGE plpgsql;
942
+
943
+ DROP FUNCTION IF EXISTS unit_tests.begin_junit(verbosity integer);
944
+ CREATE FUNCTION unit_tests.begin_junit(verbosity integer DEFAULT 9)
945
+ RETURNS TABLE(message text, result character(1))
946
+ AS
947
+ $$
948
+ BEGIN
949
+ RETURN QUERY
950
+ SELECT * FROM unit_tests.begin($1, 'junit');
951
+ END
952
+ $$
953
+ LANGUAGE plpgsql;
954
+
955
+ -- version of begin that will raise if any tests have failed
956
+ -- this will cause psql to return nonzeo exit code so the build/script can be halted
957
+ CREATE OR REPLACE FUNCTION unit_tests.begin_psql(verbosity integer default 9, format text default '')
958
+ RETURNS VOID AS $$
959
+ DECLARE
960
+ _msg text;
961
+ _res character(1);
962
+ BEGIN
963
+ SELECT * INTO _msg, _res
964
+ FROM unit_tests.begin(verbosity, format)
965
+ ;
966
+ IF(_res != 'Y') THEN
967
+ RAISE EXCEPTION 'Tests failed [%]', _msg;
968
+ END IF;
969
+ END
970
+ $$
971
+ LANGUAGE plpgsql;
972
+