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.
- checksums.yaml +7 -0
- data/.gitmodules +3 -0
- data/LICENSE +26 -0
- data/README.md +50 -0
- data/bin/sem-add-framework +21 -0
- data/bin/sem-add-safe +32 -0
- data/plpgunit/frontend/readme.txt +1 -0
- data/plpgunit/install/0.uninstall-unit-test.sql +26 -0
- data/plpgunit/install/1.install-unit-test.sql +972 -0
- data/plpgunit/license.txt +19 -0
- data/plpgunit/readme.md +174 -0
- data/scripts/20170608-212033.sql +972 -0
- data/scripts/20170608-212040.sql +1 -0
- data/scripts/20170608-212341.sql +10 -0
- data/scripts/20170608-212348.sql +23 -0
- data/scripts/20170608-212359.sql +9 -0
- data/scripts/20170608-212413.sql +9 -0
- data/scripts/20170608-212430.sql +34 -0
- data/scripts/20170608-212759.sql +24 -0
- data/scripts/20170608-212803.sql +23 -0
- data/scripts/20170608-212813.sql +75 -0
- data/scripts/20170608-212819.sql +81 -0
- data/scripts/20170608-212825.sql +59 -0
- data/scripts/20170609-183531.sql +12 -0
- data/src/functions/array_distinct.sql +10 -0
- data/src/functions/array_from_json.sql +23 -0
- data/src/functions/check_http_url.sql +9 -0
- data/src/functions/check_url.sql +9 -0
- data/src/functions/create_unique_name.sql +34 -0
- data/src/functions/trf_created_at_and_updated_at.sql +12 -0
- data/src/schema_create.sql +1 -0
- data/tests/functions/array_distinct.sql +24 -0
- data/tests/functions/array_from_json.sql +23 -0
- data/tests/functions/check_http_url.sql +75 -0
- data/tests/functions/check_url.sql +81 -0
- data/tests/functions/create_unique_name.sql +59 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -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
|
data/.gitmodules
ADDED
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.
|
data/README.md
ADDED
@@ -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
|
data/bin/sem-add-safe
ADDED
@@ -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
|
+
|