Skip to content

Commit a94a4b5

Browse files
committed
docs: add demo with partial coverage
1 parent 4d197ff commit a94a4b5

4 files changed

Lines changed: 294 additions & 0 deletions

File tree

examples/demo/01_schema.sql

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-- Demo Schema: Simple Product Inventory System
2+
--
3+
-- This file sets up the products table and seeds a few rows so the
4+
-- functions in 02_functions.sql have data to work with.
5+
6+
CREATE TABLE products (
7+
id SERIAL PRIMARY KEY,
8+
name TEXT NOT NULL,
9+
price NUMERIC NOT NULL,
10+
stock_qty INT NOT NULL DEFAULT 0,
11+
category TEXT NOT NULL DEFAULT 'general'
12+
);
13+
14+
-- Seed data: a variety of stock levels to exercise get_stock_status branches
15+
INSERT INTO products (name, price, stock_qty, category) VALUES
16+
('Laptop', 999.99, 25, 'electronics'), -- in_stock
17+
('Headphones', 49.99, 8, 'electronics'), -- low_stock
18+
('Desk Chair', 199.99, 0, 'furniture'), -- out_of_stock
19+
('Notebook', 4.99, 200, 'stationery'); -- in_stock

examples/demo/02_functions.sql

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
-- Demo Functions: Product Management
2+
--
3+
-- Three PL/pgSQL functions designed to demonstrate pgcov branch coverage.
4+
-- Key design: IF/ELSIF/ELSE branches use variable assignments (not RETURN)
5+
-- so that coverage signals are *reachable* — they fire inside the preceding
6+
-- branch body after the assignment, before ELSIF/ELSE takes over.
7+
8+
-- ---------------------------------------------------------------------------
9+
-- add_product
10+
-- Validates inputs then inserts a new product row.
11+
-- Uses simple guard IFs — the NOTIFY fires before each IF check, so the
12+
-- validation guards register as covered on every successful call.
13+
-- ---------------------------------------------------------------------------
14+
CREATE OR REPLACE FUNCTION add_product(
15+
p_name TEXT,
16+
p_price NUMERIC,
17+
p_stock INT DEFAULT 0,
18+
p_category TEXT DEFAULT 'general'
19+
) RETURNS INT AS $$
20+
DECLARE
21+
v_id INT;
22+
BEGIN
23+
IF p_name IS NULL OR p_name = '' THEN
24+
RAISE EXCEPTION 'Product name cannot be empty';
25+
END IF;
26+
27+
IF p_price < 0 THEN
28+
RAISE EXCEPTION 'Price must be non-negative, got %', p_price;
29+
END IF;
30+
31+
IF p_stock < 0 THEN
32+
RAISE EXCEPTION 'Stock must be non-negative, got %', p_stock;
33+
END IF;
34+
35+
INSERT INTO products (name, price, stock_qty, category)
36+
VALUES (p_name, p_price, p_stock, p_category)
37+
RETURNING id INTO v_id;
38+
39+
RETURN v_id;
40+
END;
41+
$$ LANGUAGE plpgsql;
42+
43+
-- ---------------------------------------------------------------------------
44+
-- get_stock_status
45+
-- Returns a human-readable stock level for the given product.
46+
-- Uses a result *variable* (not RETURN inside each branch) so that the
47+
-- coverage signal for each ELSIF/ELSE clause fires when the *preceding*
48+
-- branch executes — making uncovered branches clearly visible.
49+
--
50+
-- Branch signals:
51+
-- 'out_of_stock' signal → only fires when stock = 0
52+
-- 'low_stock' signal → only fires when 0 < stock ≤ 10
53+
-- (both are missed by the basic test that uses stock = 50)
54+
-- ---------------------------------------------------------------------------
55+
CREATE OR REPLACE FUNCTION get_stock_status(
56+
p_product_id INT
57+
) RETURNS TEXT AS $$
58+
DECLARE
59+
v_stock INT;
60+
v_result TEXT;
61+
BEGIN
62+
SELECT stock_qty INTO v_stock
63+
FROM products
64+
WHERE id = p_product_id;
65+
66+
IF NOT FOUND THEN
67+
RETURN 'unknown';
68+
END IF;
69+
70+
IF v_stock = 0 THEN
71+
v_result := 'out_of_stock';
72+
ELSIF v_stock <= 10 THEN
73+
v_result := 'low_stock';
74+
ELSE
75+
v_result := 'in_stock';
76+
END IF;
77+
78+
RETURN v_result;
79+
END;
80+
$$ LANGUAGE plpgsql;
81+
82+
-- ---------------------------------------------------------------------------
83+
-- apply_pricing_policy
84+
-- Returns the final price after applying a customer-tier discount.
85+
-- Uses the same result-variable pattern: each ELSIF/ELSE signal fires
86+
-- inside the *preceding* tier's branch, so only tiers you actually test
87+
-- will register as covered.
88+
--
89+
-- Branch signals:
90+
-- 'premium' signal → only fires when tier = 'vip' (initial test)
91+
-- 'standard' signal → only fires when tier = 'premium' (needs comment-out)
92+
-- 'none' signal → only fires when tier = 'standard'(needs comment-out)
93+
-- ---------------------------------------------------------------------------
94+
CREATE OR REPLACE FUNCTION apply_pricing_policy(
95+
p_product_id INT,
96+
p_tier TEXT DEFAULT 'none'
97+
) RETURNS NUMERIC AS $$
98+
DECLARE
99+
v_price NUMERIC;
100+
v_discount NUMERIC;
101+
BEGIN
102+
SELECT price INTO v_price
103+
FROM products
104+
WHERE id = p_product_id;
105+
106+
IF NOT FOUND THEN
107+
RAISE EXCEPTION 'Product % not found', p_product_id;
108+
END IF;
109+
110+
IF p_tier = 'vip' THEN
111+
v_discount := 0.25;
112+
ELSIF p_tier = 'premium' THEN
113+
v_discount := 0.15;
114+
ELSIF p_tier = 'standard' THEN
115+
v_discount := 0.05;
116+
ELSE
117+
v_discount := 0.00;
118+
END IF;
119+
120+
RETURN ROUND(v_price * (1 - v_discount), 2);
121+
END;
122+
$$ LANGUAGE plpgsql;

examples/demo/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Demo Example: Product Inventory System
2+
3+
This example shows **pgcov** catching uncovered branches in PL/pgSQL functions — a
4+
situation that happens all the time in real projects.
5+
6+
## What's Here
7+
8+
| File | Description |
9+
| ------ | ------------- |
10+
| `01_schema.sql` | `products` table + seed data |
11+
| `02_functions.sql` | Three PL/pgSQL functions with input-validation branches |
12+
| `demo_test.sql` | Tests — initially happy-path only, expandable to full coverage |
13+
14+
## The Schema
15+
16+
A simple product inventory with three management functions:
17+
18+
```
19+
add_product(name, price, stock, category) → INT
20+
Branches: empty name | negative price | negative stock | happy path
21+
22+
get_stock_status(product_id) → TEXT
23+
Branches: not found ('unknown') | stock = 0 ('out_of_stock')
24+
| stock ≤ 10 ('low_stock') | else ('in_stock')
25+
26+
apply_discount(product_id, discount_pct) → NUMERIC
27+
Branches: discount out of range | product not found | happy path
28+
```
29+
30+
## Demo Walkthrough
31+
32+
### Step 1 — Look at the schema
33+
34+
Browse `01_schema.sql` and `02_functions.sql`.
35+
Each function has several `IF` branches. Which ones are actually exercised?
36+
37+
### Step 2 — Run pgcov (partial coverage)
38+
39+
```bash
40+
# From the repository root
41+
pgcov run -c "postgresql://user@host/postgres?sslmode=disable" ./examples/demo/
42+
pgcov report --format html -o report-partial.html
43+
```
44+
45+
Open `report-partial.html`. The validation branches are highlighted — they are
46+
**never reached** by the basic happy-path tests.
47+
48+
### Step 3 — Uncomment the edge-case tests
49+
50+
In `demo_test.sql`, remove the `--` prefix from every line of the second `DO` block
51+
(the one that starts with `-- DO $$`).
52+
53+
### Step 4 — Run pgcov again (full coverage)
54+
55+
```bash
56+
pgcov run -c "postgresql://user@host/postgres?sslmode=disable" ./examples/demo/
57+
pgcov report --format html -o report-full.html
58+
```
59+
60+
Open `report-full.html`. Every branch in every function is now covered.
61+
62+
## Quick Start
63+
64+
```bash
65+
# Build the tool (once)
66+
go build ./cmd/pgcov
67+
68+
# --- Partial run ---
69+
./pgcov run -c "postgresql://pasha@127.0.0.1/postgres?sslmode=disable" ./examples/demo/
70+
./pgcov report --format html -o report-partial.html
71+
72+
# Edit demo_test.sql: uncomment the second DO block
73+
74+
# --- Full run ---
75+
./pgcov run -c "postgresql://pasha@127.0.0.1/postgres?sslmode=disable" ./examples/demo/
76+
./pgcov report --format html -o report-full.html
77+
```

examples/demo/demo_test.sql

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
-- Demo Tests: Product Inventory System
2+
--
3+
-- Run with:
4+
-- pgcov run -c "postgresql://user@host/postgres?sslmode=disable" ./examples/demo/
5+
--
6+
-- STEP 1: Run as-is to observe partial coverage (~80 %).
7+
-- STEP 2: Uncomment the second DO block and re-run to reach 100 %.
8+
9+
-- Basic tests: happy-path only
10+
--
11+
-- Covers:
12+
-- [x] add_product - all 5 signals (guard IFs + INSERT + RETURN fire on every call)
13+
-- [x] get_stock_status - SELECT, guard IF, stock-level IF block start, RETURN
14+
-- [ ] get_stock_status - 'out_of_stock' and 'low_stock' branch signals NOT hit
15+
-- [x] apply_pricing_policy - SELECT, guard IF, tier IF block start, 'premium' signal
16+
-- [ ] apply_pricing_policy - 'standard' and 'none' branch signals NOT hit
17+
18+
DO $$
19+
DECLARE
20+
v_id INT;
21+
v_price NUMERIC;
22+
BEGIN
23+
-- add_product: normal case
24+
v_id := add_product('Keyboard', 79.99, 50, 'electronics');
25+
ASSERT v_id IS NOT NULL, 'Expected a valid product ID';
26+
27+
-- get_stock_status: in_stock branch (stock_qty = 50)
28+
ASSERT get_stock_status(v_id) = 'in_stock',
29+
format('Expected in_stock, got %s', get_stock_status(v_id));
30+
31+
-- apply_pricing_policy: vip tier (25 % off)
32+
v_price := apply_pricing_policy(v_id, 'vip');
33+
ASSERT v_price < 79.99, format('Expected discounted price, got %s', v_price);
34+
35+
RAISE NOTICE 'Basic tests passed -- run pgcov to see which branches are not covered!';
36+
END $$;
37+
38+
39+
-- Branch tests: uncomment to reach 100 % coverage
40+
--
41+
-- How the signals work:
42+
-- In IF/ELSIF/ELSE chains with variable assignments, pgcov injects a NOTIFY
43+
-- *inside* each branch (after the assignment). That NOTIFY only fires when
44+
-- the BRANCH ABOVE IT is taken. So "out_of_stock" coverage signal fires only
45+
-- when stock = 0; "low_stock" signal fires only when 0 < stock <= 10; etc.
46+
47+
-- DO $$
48+
-- DECLARE
49+
-- v_id INT;
50+
-- v_price NUMERIC;
51+
-- BEGIN
52+
-- -- get_stock_status: triggers the 'out_of_stock' branch signal
53+
-- v_id := add_product('Empty Shelf', 1.00, 0);
54+
-- ASSERT get_stock_status(v_id) = 'out_of_stock',
55+
-- format('Expected out_of_stock, got %s', get_stock_status(v_id));
56+
--
57+
-- -- get_stock_status: triggers the 'low_stock' branch signal
58+
-- v_id := add_product('Rare Find', 1.00, 5);
59+
-- ASSERT get_stock_status(v_id) = 'low_stock',
60+
-- format('Expected low_stock, got %s', get_stock_status(v_id));
61+
--
62+
-- -- apply_pricing_policy: premium tier (15 % off)
63+
-- -- triggers the 'standard' ELSIF signal (fires inside the premium branch)
64+
-- v_id := add_product('Priced Item', 100.00, 20);
65+
-- v_price := apply_pricing_policy(v_id, 'premium');
66+
-- ASSERT v_price = 85.00,
67+
-- format('Expected 85.00, got %s', v_price);
68+
--
69+
-- -- apply_pricing_policy: standard tier (5 % off)
70+
-- -- triggers the 'none' ELSE signal (fires inside the standard branch)
71+
-- v_price := apply_pricing_policy(v_id, 'standard');
72+
-- ASSERT v_price = 95.00,
73+
-- format('Expected 95.00, got %s', v_price);
74+
--
75+
-- RAISE NOTICE 'All branch tests passed -- 100%% coverage achieved!';
76+
-- END $$;

0 commit comments

Comments
 (0)