diff --git a/Makefile b/Makefile index 6718719b..8c54ef1c 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ ifneq ("$(wildcard *.yaml)","") $(error Move away any config file) endif +ifneq ("$(wildcard *.sch)","") + $(error Move away any schematic file) +endif + deb: perl debian/make_postinst.pl > debian/postinst chmod +x debian/postinst diff --git a/tests/board_samples/kibom-test.kicad_pcb b/tests/board_samples/kibom-test.kicad_pcb new file mode 100644 index 00000000..e69de29b diff --git a/tests/board_samples/kibom-test.sch b/tests/board_samples/kibom-test.sch new file mode 100644 index 00000000..c1e4dd66 --- /dev/null +++ b/tests/board_samples/kibom-test.sch @@ -0,0 +1,180 @@ +EESchema Schematic File Version 4 +EELAYER 30 0 +EELAYER END +$Descr A4 11693 8268 +encoding utf-8 +Sheet 1 1 +Title "KiBom Test Schematic" +Date "2020-03-12" +Rev "A" +Comp "https://github.com/SchrodingersGat/KiBom" +Comment1 "" +Comment2 "" +Comment3 "" +Comment4 "" +$EndDescr +$Comp +L Device:R R1 +U 1 1 5E6A2873 +P 2200 2550 +F 0 "R1" V 2280 2550 50 0000 C CNN +F 1 "10K" V 2200 2550 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2130 2550 50 0001 C CNN +F 3 "~" H 2200 2550 50 0001 C CNN + 1 2200 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R2 +U 1 1 5E6A330D +P 2500 2550 +F 0 "R2" V 2580 2550 50 0000 C CNN +F 1 "10K" V 2500 2550 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2430 2550 50 0001 C CNN +F 3 "~" H 2500 2550 50 0001 C CNN + 1 2500 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R3 +U 1 1 5E6A35E1 +P 2750 2550 +F 0 "R3" V 2830 2550 50 0000 C CNN +F 1 "10K" V 2750 2550 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2680 2550 50 0001 C CNN +F 3 "~" H 2750 2550 50 0001 C CNN + 1 2750 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R4 +U 1 1 5E6A37B2 +P 3000 2550 +F 0 "R4" V 3080 2550 50 0000 C CNN +F 1 "10K" V 3000 2550 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2930 2550 50 0001 C CNN +F 3 "~" H 3000 2550 50 0001 C CNN + 1 3000 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R5 +U 1 1 5E6A39EB +P 3250 2550 +F 0 "R5" V 3330 2550 50 0000 C CNN +F 1 "10K" V 3250 2550 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 3180 2550 50 0001 C CNN +F 3 "~" H 3250 2550 50 0001 C CNN + 1 3250 2550 + 1 0 0 -1 +$EndComp +Text Notes 3500 2550 0 50 ~ 0 +5 x 10K resistors in 0805 package +$Comp +L Device:R R6 +U 1 1 5E6A3CA0 +P 2200 3100 +F 0 "R6" V 2280 3100 50 0000 C CNN +F 1 "4K7" V 2200 3100 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2130 3100 50 0001 C CNN +F 3 "~" H 2200 3100 50 0001 C CNN +F 4 "DNF" V 2200 3100 50 0001 C CNN "Config" + 1 2200 3100 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R7 +U 1 1 5E6A3F38 +P 2500 3100 +F 0 "R7" V 2580 3100 50 0000 C CNN +F 1 "4700" V 2500 3100 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2430 3100 50 0001 C CNN +F 3 "~" H 2500 3100 50 0001 C CNN +F 4 "DNC" V 2500 3100 50 0001 C CNN "Config" + 1 2500 3100 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R8 +U 1 1 5E6A4181 +P 2750 3100 +F 0 "R8" V 2830 3100 50 0000 C CNN +F 1 "4.7K" V 2750 3100 50 0000 C CNN +F 2 "Resistor_SMD:R_0805_2012Metric" V 2680 3100 50 0001 C CNN +F 3 "~" H 2750 3100 50 0001 C CNN + 1 2750 3100 + 1 0 0 -1 +$EndComp +Text Notes 3500 3150 0 50 ~ 0 +3 x 4K7 resistors in 0805 package\nNote: Values are identical even if specified differently +$Comp +L Device:R R9 +U 1 1 5E6A448B +P 2200 3650 +F 0 "R9" V 2280 3650 50 0000 C CNN +F 1 "4K7" V 2200 3650 50 0000 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric" V 2130 3650 50 0001 C CNN +F 3 "~" H 2200 3650 50 0001 C CNN + 1 2200 3650 + 1 0 0 -1 +$EndComp +$Comp +L Device:R R10 +U 1 1 5E6A491A +P 2500 3650 +F 0 "R10" V 2580 3650 50 0000 C CNN +F 1 "4K7" V 2500 3650 50 0000 C CNN +F 2 "Resistor_SMD:R_0603_1608Metric" V 2430 3650 50 0001 C CNN +F 3 "~" H 2500 3650 50 0001 C CNN + 1 2500 3650 + 1 0 0 -1 +$EndComp +Text Notes 3500 3650 0 50 ~ 0 +3 x 4K7 resistors in 0603 package +Text Notes 550 950 0 50 ~ 0 +This schematic serves as a test-file for the KiBom export script.\n\nAfter making a change to the schematic, remember to re-export the BOM to generate the intermediate .xml file\n\n(The testing framework cannot perform the netlist-export step!) +$Comp +L Device:C C1 +U 1 1 5E6A62CC +P 6650 2550 +F 0 "C1" H 6675 2650 50 0000 L CNN +F 1 "10nF" H 6675 2450 50 0000 L CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 6688 2400 50 0001 C CNN +F 3 "~" H 6650 2550 50 0001 C CNN + 1 6650 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C2 +U 1 1 5E6A6854 +P 7050 2550 +F 0 "C2" H 7075 2650 50 0000 L CNN +F 1 "10n" H 7075 2450 50 0000 L CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 7088 2400 50 0001 C CNN +F 3 "~" H 7050 2550 50 0001 C CNN + 1 7050 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C3 +U 1 1 5E6A6A34 +P 7450 2550 +F 0 "C3" H 7475 2650 50 0000 L CNN +F 1 "0.01uF" H 7475 2450 50 0000 L CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 7488 2400 50 0001 C CNN +F 3 "~" H 7450 2550 50 0001 C CNN + 1 7450 2550 + 1 0 0 -1 +$EndComp +$Comp +L Device:C C4 +U 1 1 5E6A6CB6 +P 7900 2550 +F 0 "C4" H 7925 2650 50 0000 L CNN +F 1 "0.01uf" H 7925 2450 50 0000 L CNN +F 2 "Capacitor_SMD:C_0603_1608Metric" H 7938 2400 50 0001 C CNN +F 3 "~" H 7900 2550 50 0001 C CNN + 1 7900 2550 + 1 0 0 -1 +$EndComp +$EndSCHEMATC diff --git a/tests/test_plot/test_int_bom.py b/tests/test_plot/test_int_bom.py new file mode 100644 index 00000000..fd183cce --- /dev/null +++ b/tests/test_plot/test_int_bom.py @@ -0,0 +1,104 @@ +""" +Tests of Internal BoM files + +For debug information use: +pytest-3 --log-cli-level debug + +""" + +import os +import sys +import logging +from copy import deepcopy +# Look for the 'utils' module from where the script is running +prev_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +if prev_dir not in sys.path: + sys.path.insert(0, prev_dir) +# Utils import +from utils import context +prev_dir = os.path.dirname(prev_dir) +if prev_dir not in sys.path: + sys.path.insert(0, prev_dir) +# from kiplot.misc import (BOM_ERROR) + +BOM_DIR = 'BoM' +REF_COLUMN_NAME = 'Reference' +QTY_COLUMN_NAME = 'Quantity Per PCB' +KIBOM_TEST_HEAD = ['Component', 'Description', 'Part', REF_COLUMN_NAME, 'Value', 'Footprint', QTY_COLUMN_NAME, 'Datasheet', + 'config'] +KIBOM_TEST_COMPONENTS = ['C1', 'C2', 'C3', 'C4', 'R1', 'R2', 'R3', 'R4', 'R5', 'R7', 'R8', 'R9', 'R10'] +KIBOM_TEST_EXCLUDE = ['R6'] +KIBOM_TEST_GROUPS = 5 + + +def check_kibom_test_netlist(rows, ref_column, groups, exclude, comps): + """ Checks the kibom-test.xml expected results """ + # Groups + assert len(rows) == groups + logging.debug(str(groups) + " groups OK") + # Components + if comps: + components = [] + for r in rows: + components.extend(r[ref_column].split(' ')) + assert len(components) == len(comps) + logging.debug(str(len(comps)) + " components OK") + # Excluded + if exclude: + for ex in exclude: + assert ex not in components + logging.debug(str(len(exclude)) + " not fitted OK") + # All the other components + if comps: + for c in comps: + assert c in components + logging.debug("list of components OK") + + +def check_dnc(rows, comp, ref, qty): + for row in rows: + if row[ref].find(comp) != -1: + assert row[qty] == '1 (DNC)' + logging.debug(comp + " is DNC OK") + return + + +def test_int_bom_simple_csv(): + prj = 'kibom-test' + ext = 'csv' + ctx = context.TestContext('test_int_bom_simple_csv', prj, 'int_bom_simple_csv', BOM_DIR) + ctx.run(no_board_file=True, extra=['-e', os.path.join(ctx.get_board_dir(), prj+'.sch')]) + out = prj + '-bom.' + ext + rows, header = ctx.load_csv(out) + assert header == KIBOM_TEST_HEAD + ref_column = header.index(REF_COLUMN_NAME) + qty_column = header.index(QTY_COLUMN_NAME) + check_kibom_test_netlist(rows, ref_column, KIBOM_TEST_GROUPS, KIBOM_TEST_EXCLUDE, KIBOM_TEST_COMPONENTS) + check_dnc(rows, 'R7', ref_column, qty_column) + ctx.clean_up() + + +def test_int_bom_simple_html(): + prj = 'kibom-test' + ext = 'html' + ctx = context.TestContext('test_int_bom_simple_html', prj, 'int_bom_simple_html', BOM_DIR) + ctx.run(no_board_file=True, extra=['-e', os.path.join(ctx.get_board_dir(), prj+'.sch')]) + out = prj + '-bom.' + ext + rows, headers = ctx.load_html(out) + # Test we got the normal and DNF tables + assert len(rows) == 2 + assert len(headers) == 2 + # Test both tables has the same headings and they are the expected + assert headers[0] == headers[1] + head_no_comp = deepcopy(KIBOM_TEST_HEAD) + head_no_comp[0] = '' # HTML numbered column doesn't have a name + assert headers[0] == head_no_comp + # Look for reference and quantity columns + ref_column = headers[0].index(REF_COLUMN_NAME) + qty_column = headers[0].index(QTY_COLUMN_NAME) + # Check the normal table + check_kibom_test_netlist(rows[0], ref_column, KIBOM_TEST_GROUPS, KIBOM_TEST_EXCLUDE, KIBOM_TEST_COMPONENTS) + check_dnc(rows[0], 'R7', ref_column, qty_column) + # Check the DNF table + check_kibom_test_netlist(rows[1], ref_column, 1, KIBOM_TEST_COMPONENTS, KIBOM_TEST_EXCLUDE) + ctx.clean_up() diff --git a/tests/utils/context.py b/tests/utils/context.py index fa7c40a2..e17c103d 100644 --- a/tests/utils/context.py +++ b/tests/utils/context.py @@ -5,6 +5,7 @@ import logging import subprocess import re import pytest +import csv from glob import glob from pty import openpty @@ -339,6 +340,41 @@ class TestContext(object): logging.debug("Found apertures {}".format(aps)) return aps + def load_csv(self, filename, column=3): + rows = [] + with open(self.expect_out_file(os.path.join(self.sub_dir, filename))) as csvfile: + reader = csv.reader(csvfile) + header = next(reader) # Skip header + for r in reader: + if not r: + break + rows.append(r) + return rows, header + + def load_html(self, filename, column=4, split=True): + file = self.expect_out_file(os.path.join(self.sub_dir, filename)) + with open(file) as f: + html = f.read() + rows = [[], []] + headers = [[], []] + c = 0 + for body in re.findall(r'((?:\s+.*?)+)', html, re.MULTILINE): + if c: + # Header + m = re.search(r'\s+((?:(?:.*)\s+)+)', body, re.MULTILINE) + assert m, 'Failed to get table header' + head = m.group(1) + for col_name in re.findall(r'(.*)', head): + headers[c-1].append(col_name) + # Rows + for row in re.findall(r'\s+((?:(?:.*)\s+)+)', body, re.MULTILINE): + r = [] + for cell in re.findall(r'(.*?)', row, re.MULTILINE): + r.append(cell) + rows[c-1].append(r) + c += 1 + return rows, headers + class TestContextSCH(TestContext): diff --git a/tests/yaml_samples/int_bom_simple_csv.kiplot.yaml b/tests/yaml_samples/int_bom_simple_csv.kiplot.yaml new file mode 100644 index 00000000..cbd90905 --- /dev/null +++ b/tests/yaml_samples/int_bom_simple_csv.kiplot.yaml @@ -0,0 +1,12 @@ +# Example KiPlot config file +kiplot: + version: 1 + +outputs: + - name: 'bom_internal' + comment: "Bill of Materials in CSV format" + type: bom + dir: BoM + options: + format: CSV + diff --git a/tests/yaml_samples/int_bom_simple_html.kiplot.yaml b/tests/yaml_samples/int_bom_simple_html.kiplot.yaml new file mode 100644 index 00000000..a4578fb4 --- /dev/null +++ b/tests/yaml_samples/int_bom_simple_html.kiplot.yaml @@ -0,0 +1,12 @@ +# Example KiPlot config file +kiplot: + version: 1 + +outputs: + - name: 'bom_internal' + comment: "Bill of Materials in HTML format" + type: bom + dir: BoM + options: + format: HTML +