diff --git a/database/create.sh b/database/create.sh index 9016ad3d5f19803e64f0e97b70c4a7e7428c8374..5a65f1c9e27b1aa0f0e05f7aeb193f97bb5f50e9 100755 --- a/database/create.sh +++ b/database/create.sh @@ -22,3 +22,7 @@ done for file in $(ls -B data/); do psql $DB_NAME -f data/$file done + +for file in $(ls -B query/); do + psql $DB_NAME -f query/$file +done diff --git a/database/create/001-proinfo-oltp.sql b/database/create/001-proinfo-oltp.sql index 6f5e7df328cd946b35f421383e0e08141eef0389..80cdd68577a4f3bbaa557e0fec5e2ac7459c9cf7 100644 --- a/database/create/001-proinfo-oltp.sql +++ b/database/create/001-proinfo-oltp.sql @@ -22,10 +22,10 @@ CREATE TABLE rejected_inventory ( id INTEGER, sch_id INTEGER, cit_id INTEGER, - contact_date DATE, - project project_enum, - inep CHARACTER VARYING(18) not null, - macaddr macaddr, + contact_date DATE NOT NULL, + project INTEGER, + inep CHARACTER VARYING(18) NOT NULL, + macaddr TEXT NOT NULL, os_type TEXT, os_distro TEXT, os_kernel TEXT, diff --git a/database/load/000-sa.sql b/database/load/000-sa.sql index f999fa14d4ad9fa443e3975e9250783e2c99ce6c..c18f36cfda0eda76133f2734298c21fb7cccc8fc 100644 --- a/database/load/000-sa.sql +++ b/database/load/000-sa.sql @@ -7,6 +7,9 @@ BEGIN END; $$ language plpgsql; +CREATE OR REPLACE FUNCTION valid_macaddress(mac TEXT) RETURNS BOOLEAN AS $$ + SELECT $1 ~* '(([0-9]|[a-f]){2}:){5}([0-9]|[a-f]){2}' AND $1 <> '00:00:00:00:00:00'; +$$ LANGUAGE SQL; -- Creates inventory staging area. This function should NOT be called directly. CREATE OR REPLACE FUNCTION sa_inventory_create() returns void as $$ @@ -31,13 +34,9 @@ BEGIN s.id as sch_id, s.cit_id, i.contact_date, - CASE WHEN i.project = 0 THEN 'proinfo'::project_enum - WHEN i.project = 1 THEN 'uca_classmate'::project_enum - WHEN i.project = 2 THEN 'uca_server'::project_enum - WHEN i.project = 3 THEN 'projector'::project_enum - END as project, + i.project, i.inep, - CAST(i.macaddr as MACADDR), + i.macaddr, i.os_type, i.os_distro, i.os_kernel, @@ -66,7 +65,7 @@ BEGIN VALUES ('create sa inventory', start_ts, CLOCK_TIMESTAMP(), total_rows); -- truncating OLTP table - --TRUNCATE TABLE proinfo_inventory; + TRUNCATE TABLE proinfo_inventory; END; $$ language plpgsql; @@ -161,8 +160,6 @@ $$ language plpgsql; -- sanitize, removing invalid rows --- TODO: Here we need to remove and log into another table rows that don't --- relate to any school on dim_school. See LEFT JOIN on prior functions. CREATE OR REPLACE FUNCTION sa_sanitize() returns void as $$ BEGIN INSERT INTO rejected_inventory @@ -175,7 +172,9 @@ BEGIN disk1_model is NULL OR disk1_size is NULL OR disk1_used is NULL OR - macaddr = '00:00:00:00:00:00'::macaddr); + NOT valid_macaddress(macaddr) OR + project < 0 OR project > 3 OR + inep NOT IN (SELECT inep FROM dim_school)); DELETE FROM sa_inventory WHERE id IN (SELECT id FROM rejected_inventory); END; diff --git a/database/load/002-dim_inventory.sql b/database/load/002-dim_inventory.sql index 3acc283fa63bb60f5770de1ae2f118e0761517e7..5a79a811e6b472d088ae9e217699efea94b3fc44 100644 --- a/database/load/002-dim_inventory.sql +++ b/database/load/002-dim_inventory.sql @@ -1,5 +1,14 @@ -- loads the table dim_inventory with inventories of new computers -- or inventories that had been modified. + +CREATE OR REPLACE FUNCTION to_project_enum(i INTEGER) RETURNS project_enum AS $$ +SELECT CASE WHEN $1 = 0 THEN 'proinfo'::project_enum + WHEN $1 = 1 THEN 'uca_classmate'::project_enum + WHEN $1 = 2 THEN 'uca_server'::project_enum + WHEN $1 = 3 THEN 'projector'::project_enum + END as project; +$$ LANGUAGE SQL; + CREATE OR REPLACE FUNCTION load_dim_inventory() returns void as $$ DECLARE inv_row record; @@ -12,7 +21,7 @@ BEGIN -- at the end of the function. FOR inv_row IN SELECT * FROM sa_inventory s LEFT JOIN (SELECT * FROM dim_inventory WHERE is_current = '1') d ON s.sch_id = d.sch_id AND - s.macaddr = d.macaddr AND NOT + s.macaddr::macaddr = d.macaddr AND NOT (s.memory >= d.memory * 0.9 AND s.memory <= d.memory * 1.1 AND s.processor = d.processor AND @@ -39,8 +48,8 @@ BEGIN disk1_model, disk1_size, disk1_used, disk2_model, disk2_size, disk2_used, memory, processor, os_type, os_distro, os_kernel, is_current) VALUES - (inv_row.sch_id, inv_row.macaddr, - inv_row.contact_date, inv_row.project, + (inv_row.sch_id, inv_row.macaddr::macaddr, + inv_row.contact_date, to_project_enum(inv_row.project), inv_row.disk1_model, inv_row.disk1_size, inv_row.disk1_used, inv_row.disk2_model, inv_row.disk2_size, inv_row.disk2_used, diff --git a/database/load/003-fact_contact.sql b/database/load/003-fact_contact.sql index 8053fad7fb36027cbe4de8efcfab8d2f701c6258..c9cb914448b614ed35a2a7a99902a1221a795895 100644 --- a/database/load/003-fact_contact.sql +++ b/database/load/003-fact_contact.sql @@ -16,7 +16,7 @@ BEGIN -- INSERT INTO fact_contact (sch_id, cit_id, dat_id, macaddr) - (SELECT sch_id, cit_id, contact_date, macaddr FROM sa_inventory) + (SELECT sch_id, cit_id, contact_date, macaddr::macaddr FROM sa_inventory) EXCEPT (SELECT sch_id, cit_id, dat_id, macaddr FROM fact_contact WHERE dat_id >= result.min_date AND dat_id <= result.max_date); diff --git a/database/query/availability.sql b/database/query/availability.sql new file mode 100644 index 0000000000000000000000000000000000000000..c3a9f6f10c2058cf2337f59cce2d657965fb627f --- /dev/null +++ b/database/query/availability.sql @@ -0,0 +1,213 @@ +/*Brasil*/ +create or replace function is_within(dat date, low integer, high integer) returns integer as $$ +select (case when $1 > current_date - $2 and $1 < current_date - $3 then 1 else 0 END); +$$ language sql; + +create or replace function is_green(date) returns integer as $$ +select is_within($1, 10, 0); +$$ language sql; + +create or replace function is_yellow(date) returns integer as $$ +select is_within($1, 20, 10); +$$ language sql; + +create or replace function is_red(date) returns integer as $$ +select is_within($1, 30, 20); +$$ language sql; + +create or replace function is_black(date) returns integer as $$ +select (case when $1 < current_date - 30 then 1 else 0 END) as black +$$ language sql; + +create or replace function in_date_range(date) returns boolean as $$ +select (case when $1 > current_date - interval '6 months' then true else false END); +$$ language sql; + +create or replace function get_color(date) returns text as $$ +select (case when is_green($1) = 1 then 'green' else + case when is_yellow($1) = 1 then 'yellow' else + case when is_red($1) = 1 then 'red' else 'black' END END END); +$$ language sql; + +create or replace function days_lastContact(date) returns int as $$ +select (current_date - $1); +$$ language sql; + +create or replace function num_contactsMonth(macaddr, integer) returns integer as $$ +select count(*)::integer as contacted from fact_contact where macaddr = $1 and sch_id = $2 and (extract(month from current_date) = extract(month from dat_id)) and (extract(year from current_date) = extract(year from dat_id)); +$$ language sql; + +/* trocado 'dat_id' por 'load_date' ( aparentemente deu certo ); substituir em todas as outras funçoes */ +create or replace function availability_brazil(proj project_enum) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint) as $$ +select sum(green) as "Verde", sum(yellow) as "Amarelo", sum(red) as "Red", sum(black) as "Preto" from ( + select is_green(load_date) as green, + is_yellow(load_date) as yellow, + is_red(load_date) as red, + is_black(load_date) as black + from dim_inventory i + where project = $1 and load_date > current_date - 40 +) as temp; +$$ language sql; + +create or replace function availability_brazil_lines(proj project_enum) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Data" date) as $$ +select sum(green), sum(yellow), sum(red), sum(black), dat_id +from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, + dat_id + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr + where project = $1 and in_date_range(dat_id) +) as temp +group by dat_id +order by dat_id; +$$ language sql; + +create or replace view school_and_location as select s.id as id, inep, s.name as school, address, cep, c.name as city, state, region +from dim_school s join dim_city c on c.id = s.cit_id; + +/*Regiao*/ +create or replace function availability_region(proj project_enum) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Regiao" text) as $$ +select sum(green), sum(yellow), sum(red), sum(black), region from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, sl.region + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr + join school_and_location sl on i.sch_id = sl.id + where project = $1 and dat_id > current_date - 40 +) as temp +group by region; +$$ language sql; + + +create or replace function availability_region_lines(proj project_enum) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Data" date) as $$ +select sum(green), sum(yellow), sum(red), sum(black), dat_id +from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, + dat_id + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr + where project = $1 and in_date_range(dat_id) +) as temp +group by dat_id +order by dat_id; +$$ language sql; + +/*Estado*/ +create or replace function availability_state(proj project_enum, region text) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Estado" text) as $$ +select sum(green), sum(yellow), sum(red), sum(black), state from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, sl.state + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr + join school_and_location sl on i.sch_id = sl.id + where project = $1 and dat_id > current_date - 40 + and region = $2 +) as temp +group by state; +$$ language sql; + +create or replace function availability_state_lines(proj project_enum, region text) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Data" date) as $$ +select sum(green), sum(yellow), sum(red), sum(black), dat_id +from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, + dat_id + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr +join school_and_location sl on i.sch_id = sl.id + where project = $1 and region = $2 and in_date_range(dat_id) +) as temp +group by dat_id +order by dat_id; +$$ language sql; + +/*Cidade*/ +create or replace function availability_city(proj project_enum, region text, state text) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Cidade" text) as $$ +select sum(green), sum(yellow), sum(red), sum(black), city from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, sl.city + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr + join school_and_location sl on i.sch_id = sl.id + where project = $1 and dat_id > current_date - 40 + and region = $2 + and state = $3 +) as temp +group by city; +$$ language sql; + +create or replace function availability_city_lines(proj project_enum, region text, state text) returns table +("Verde" bigint, "Amarelo" bigint, "Vermelho" bigint, "Preto" bigint, "Data" date) as $$ +select sum(green), sum(yellow), sum(red), sum(black), dat_id +from ( + select is_green(dat_id) as green, + is_yellow(dat_id) as yellow, + is_red(dat_id) as red, + is_black(dat_id) as black, + dat_id + from dim_inventory i join fact_contact c on i.macaddr = c.macaddr +join school_and_location sl on i.sch_id = sl.id + where project = $1 and region = $2 and state = $3 and in_date_range(dat_id) +) as temp +group by dat_id +order by dat_id; +$$ language sql; + +/*Relatório*/ + +create or replace function availability_report(proj project_enum, region text, state text, city text) returns table +("Date" date, "Maquina" macaddr, "Regiao" text, "Estado" text, "Cidade" text, "UltimoContato" date, "DiasAposUltimoContato" int, "ContatosNoMes" int, "Escola" text, "Cor" text) as $$ +select data, macad, region, state, city, ult_contact, days_last_contact, num_contacts_in_month, school, color +from ( + select (select max(end_time)::date from control) as data, + i.macaddr as macad, + region, + state, + city, + c.dat_id as ult_contact, + days_lastContact(max(c.dat_id)) as days_last_contact, + num_contactsMonth(i.macaddr, i.sch_id) as num_contacts_in_month, + school, + get_color(max(dat_id)) as color + from dim_inventory i join fact_contact c on i.sch_id = c.sch_id and i.macaddr = c.macaddr +join school_and_location sl on c.sch_id = sl.id + where project = $1 and region = $2 and state = $3 and city = $4 + group by c.dat_id, i.macaddr, i.sch_id, region, state, city, school + order by i.sch_id +) as temp; +$$ language sql; + + + +/*select to_char((select max(end_time) from control), 'DD/MM/YYYY') + as load_date, machine, initcap(region) as region, state, + initcap(city) as city, last_contact, days_last_contact, + month_contacts, initcap(school) as school, color = 'green' as + green, color = 'yellow' as yellow, color = 'red' as red*/ + + + + + + + + + + + + diff --git a/database/queries/old_sa_to_new_oltp.sql b/database/query/old_sa_to_new_oltp.sql similarity index 79% rename from database/queries/old_sa_to_new_oltp.sql rename to database/query/old_sa_to_new_oltp.sql index d2717dec5bb8131ba35c0321536e3a3a325e6500..7da1f9f37b79dd485c7e5231cc5d592fa983d6fd 100644 --- a/database/queries/old_sa_to_new_oltp.sql +++ b/database/query/old_sa_to_new_oltp.sql @@ -1,4 +1,5 @@ -BEGIN; +CREATE OR REPLACE FUNCTION migrate_old_sa() RETURNS VOID AS $$ +BEGIN INSERT INTO proinfo_inventory SELECT @@ -22,4 +23,5 @@ FROM mectb00_staging_area WHERE sa_data > '2012-07-01'; -COMMIT; \ No newline at end of file +END +$$ LANGUAGE PLPGSQL; \ No newline at end of file diff --git a/database/test/proinfo_inventory.py b/database/test/proinfo_inventory.py new file mode 100755 index 0000000000000000000000000000000000000000..7b535aed521c6ca0b511e547dca18fe030da901a --- /dev/null +++ b/database/test/proinfo_inventory.py @@ -0,0 +1,101 @@ +#!/usr/bin/python + +import psycopg2 +import sys +from datetime import date, timedelta +import unittest + +# TODO: Unit tests + +MACHINE_PEAK = 10000 +DAYS = 3 +MACHINE_DOUBLE_COMM_RATIO = 0.12 +MACHINE_MULTI_COMM_RATIO = 0.02 +MACHINE_DECREASE_RATIO = 1.1 +MULTI_HD_TOTAL = 10 +HD_MODEL = 'foo' +HD_SIZE = 100 +HD_USED = 0 +HD_EXTRA = 3 +INEP = 'PR97201405' +TEMPLATE = "INSERT INTO proinfo_inventory (contact_date, project, inep, macaddr, os_type, os_distro, os_kernel, processor, memory, disk1_model, disk1_size, disk1_used, disk2_model, disk2_size, disk2_used, extra_hds) VALUES (%s, 0, '{0}', %s, 'Linux', 'Ubuntu 10.04.4 LTS', '2.6.35-25-generic', 'Intel(R) Celeron(R) CPU E1200 @', 160, '1016076 ST3160318AS_6VY4BK6R', 140, 5, %s, %s, %s, %s);".format(INEP) +INITIAL_DATE = date(2012, 7, 1) + +def machine_increase(num): + return 10**num + +def clean_inventory(cur, machines): + cur.execute('TRUNCATE proinfo_inventory;') + for i in range(machines): + mac = gen_macaddress(i) + cur.execute('''DELETE FROM fact_contact AS f USING dim_school AS d +WHERE f.sch_id = d.id AND f.macaddr = %s AND d.inep = %s;''', (mac, INEP)) + cur.execute('''DELETE FROM dim_inventory AS i USING dim_school AS s +WHERE i.macaddr = %s AND s.id = i.sch_id AND s.inep = %s''', (mac, INEP)) + +def insert_data(cur): + for day in range(DAYS): + machines = machine_increase(day) + refresh_machines(machines, day, cur) + return max_machine(machines, DAYS - 1) + +def toHex(integer): + if integer < 16: + return '0' + hex(integer)[2:] + else: return hex(integer)[2:] + +def gen_macaddress(integer): + integer += 1 + p1 = integer & 255 + p2 = (integer & 65535) >> 8 + p3 = (integer & 16777215) >> 16 + p4 = (integer & 2147483647) >> 24 + return "00:00:" + toHex(p4) + ":" + toHex(p3) + ":" + toHex(p2) + ":" + toHex(p1) + +def refresh_machines(machines, day, cur): + contact = INITIAL_DATE + timedelta(days = day) + for i in updated_machines(machines, day): + macaddr = gen_macaddress(i) +# print "Mac {0}".format(macaddr) + if i < MULTI_HD_TOTAL: + cur.execute(TEMPLATE, (contact, macaddr, HD_MODEL, HD_SIZE, HD_USED, HD_EXTRA)) + else: + cur.execute(TEMPLATE, (contact, macaddr, None, None, None, 0)) + +def max_machine(machines, day): + return intround(machines / (MACHINE_DECREASE_RATIO * (day + 1))) + +def intround(f): + return int(round(f)) + +def updated_machines(machines, day): + repetition = intround(machines * MACHINE_DOUBLE_COMM_RATIO) + multi = intround(machines * MACHINE_MULTI_COMM_RATIO) + i = 0 +# print "Repetition {0}, multi {1}".format(repetition, multi) +# print "Max {0}".format(max_machine(machines, day)) + while i < max_machine(machines, day): + if repetition > 0: + repetition -= 1 + yield repetition + elif multi > 0: + multi -= 1 + yield multi + else: + yield i + i += 1 + +def get_connection(dbname, user): + return psycopg2.connect('dbname=' + dbname, 'user=' + user) + +if __name__ == '__main__': + if len(sys.argv) != 3: + sys.exit ("How to use: test_migration <database> <user>") + connection = get_connection(sys.argv[1], sys.argv[2]) + cur = connection.cursor() + count = insert_data(cur) + print "Machines inserted {0}".format(count) + connection.commit() + clean_inventory(cur, count) + connection.commit() + connection.close() diff --git a/database/test/test_inventory.py b/database/test/test_inventory.py new file mode 100644 index 0000000000000000000000000000000000000000..14376ca769ce9dc7a6ff7d406fde1f29b89d4a68 --- /dev/null +++ b/database/test/test_inventory.py @@ -0,0 +1,105 @@ +#!/usr/bin/python + +import unittest +import traceback +from proinfo_inventory import get_connection, clean_inventory, insert_data + +def tearDownTest(connection, cur, count): + clean_inventory(cur, count) + connection.commit() + connection.close() + + +def setUpTest(): + connection = get_connection('msl09_pro', 'postgres') + cur = connection.cursor() + inserted = insert_data(cur) + cur.execute('SELECT load_dw();') + connection.commit() + return (connection, cur, inserted) + +def test_load_count(cur, inserted): + print 'All valid machines are loaded' + cur.execute("SELECT COUNT(*) FROM (SELECT macaddr, sch_id FROM dim_inventory GROUP BY macaddr, sch_id) AS t1;") + tup = cur.fetchone() + assert tup[0] == inserted + +def test_one_per_day(cur): + print 'There is only one inventory entry per day' + cur.execute("SELECT count(*) FROM dim_inventory GROUP BY load_date, sch_id, macaddr;") + for tup in cur.fetchmany(): + assert tup[0] == 1 + +def test_one_per_day_multiple_load(cur): + print 'There is only one entry per day even if multiple loads happen' + inserted = insert_data(cur) + cur.execute("SELECT load_dw();") + cur.execute("SELECT count(*) FROM dim_inventory GROUP BY load_date, sch_id, macaddr;") + for tup in cur.fetchall(): + assert tup[0] == 1 + +def test_inventory_data(cur): + print 'All inventory data is correctly inserted' + cur.execute('SELECT COUNT(*) FROM dim_inventory;') + total = cur.fetchone()[0] + cur.execute('''SELECT COUNT(*) FROM (SELECT * FROM dim_inventory +WHERE os_type = %s AND os_distro = %s AND os_kernel = %s +AND processor = %s AND memory = %s AND disk1_model = %s AND disk1_size = %s +AND disk1_used = %s) AS t1''', + ('Linux', 'Ubuntu 10.04.4 LTS', '2.6.35-25-generic', + 'Intel(R) Celeron(R) CPU E1200 @', 160, + '1016076 ST3160318AS_6VY4BK6R', 140, 5)) + assert total == cur.fetchone()[0] + +def test_inventory_multi_hd(cur, count): + print 'Inventory is correctly inserted with many hds' + from proinfo_inventory import (HD_MODEL, HD_SIZE, HD_USED, HD_EXTRA, + MULTI_HD_TOTAL) + cur.execute('''SELECT COUNT(*) FROM (SELECT macaddr, sch_id FROM dim_inventory +WHERE os_type = %s AND os_distro = %s AND os_kernel = %s +AND processor = %s AND memory = %s AND disk1_model = %s AND disk1_size = %s +AND disk1_used = %s AND disk2_model = %s AND disk2_size = %s +AND disk2_used = %s GROUP BY macaddr, sch_id) AS t1''', + ('Linux', 'Ubuntu 10.04.4 LTS', '2.6.35-25-generic', + 'Intel(R) Celeron(R) CPU E1200 @', 160, + '1016076 ST3160318AS_6VY4BK6R', 140, 5, HD_MODEL, HD_SIZE, HD_USED)) + matching = cur.fetchone() + assert MULTI_HD_TOTAL == matching[0] + +def test_clean_proinfo_inventory(cur): + print 'proinfo_inventory is cleared after load' + cur.execute('SELECT COUNT(*) FROM proinfo_inventory;') + count = cur.fetchone()[0] + print count + assert 0 == count + +def test_move_incomplete_proinfo_inventory(cur): + print 'Incomplete inventory entries are moved to rejected_inventory' + cur.execute("INSERT INTO proinfo_inventory(inep, macaddr) VALUES ('invalid', '0:0:0:0');") + cur.execute('SELECT load_dw();') + cur.execute("SELECT COUNT(*) FROM dim_inventory AS i JOIN dim_school AS s ON sch_id = i.id WHERE inep = 'invalid';") + count = cur.fetchone()[0] + print count + assert 0 == count + +def main(): + conn, cur, count = setUpTest() + + try: + test_clean_proinfo_inventory(cur) + test_load_count(cur, count) + test_one_per_day(cur) + test_inventory_data(cur) + test_inventory_multi_hd(cur, count) + test_move_incomplete_proinfo_inventory(cur) +# test_one_per_day_multiple_load(cur) + print 'Ok' + + except AssertionError: + print 'There are failed tests ' + traceback.format_exc() + finally: + cur.execute('rollback;') + tearDownTest(conn, cur, count) + +if __name__ == '__main__': + main()