diff --git a/.coveragerc b/.coveragerc index 68167f59..83a10caf 100644 --- a/.coveragerc +++ b/.coveragerc @@ -8,7 +8,7 @@ omit = */kibot/docopt.py [report] exclude_lines = pragma: no cover - @pre_class - @output_class +# @pre_class +# @output_class # raise RuntimeError diff --git a/Makefile b/Makefile index 0a3aeb9f..19d40059 100644 --- a/Makefile +++ b/Makefile @@ -49,15 +49,28 @@ test: lint $(PY_COV) html x-www-browser htmlcov/index.html +test1: + rm -rf output + rm -f example.kiplot.yaml + rm -f example.kibot.yaml + $(PY_COV) erase + $(PYTEST) --log-cli-level debug -k "test_bom_ok" --test_dir output + $(PY_COV) report + $(PY_COV) html + #x-www-browser htmlcov/index.html + @echo "********************" Output + @cat output/*/output.txt + #@echo "********************" Error + #@cat output/*/error.txt + test_docker_local: rm -rf output $(PY_COV) erase # Run in the same directory to make the __pycache__ valid # Also change the owner of the files to the current user (we run as root like in GitHub) docker run --rm -v $(CWD):$(CWD) --workdir="$(CWD)" setsoft/kicad_auto_test:latest \ - /bin/bash -c "flake8 . --count --statistics ; pytest-3 --test_dir output ; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/" + /bin/bash -c "flake8 . --count --statistics ; pytest-3 --test_dir output ; $(PY_COV) html; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/ .coverage htmlcov/" $(PY_COV) report - $(PY_COV) html x-www-browser htmlcov/index.html docker_shell: diff --git a/kibot/macros.py b/kibot/macros.py index 4745886c..6a604fec 100644 --- a/kibot/macros.py +++ b/kibot/macros.py @@ -8,7 +8,7 @@ Macros to make the output plug-ins cleaner. """ from .gs import GS # noqa: F401 from ast import (Assign, Name, Attribute, Expr, Num, Str, NameConstant, Load, Store, UnaryOp, USub, - ClassDef, Call, ImportFrom, alias) + ClassDef, Call, ImportFrom, alias, copy_location) def document(sentences, to_source, **kw): @@ -77,6 +77,8 @@ def document(sentences, to_source, **kw): else: # pragma: no cover target = Name(id=doc_id, ctx=Store()) sentences[n] = Assign(targets=[target], value=Str(s=type_hint+s.value.s.rstrip()+post_hint)) + # Copy the line number from the original docstring + copy_location(sentences[n], s) prev = s # Return the modified AST return sentences @@ -86,14 +88,23 @@ def _do_wrap_class_register(tree, mod, base_class): if isinstance(tree, ClassDef): # Create the register call name = tree.name + l_start = tree.lineno + # Python 3.8: + # l_end = tree.end_lineno + # Python 3.7, this is good enough for our needs: + l_end = l_start + 1 reg_name = name.lower() # BaseOutput.register member: attr = Attribute(value=Name(id=base_class, ctx=Load()), attr='register', ctx=Load()) # Function call to it passing reg_name and name - do_register = Expr(value=Call(func=attr, args=[Str(s=reg_name), Name(id=name, ctx=Load())], keywords=[])) + # Put it in the last line. + do_register = Expr(value=Call(func=attr, args=[Str(s=reg_name), Name(id=name, ctx=Load())], keywords=[]), + lineno=l_end, col_offset=0, end_lineno=l_end, end_col_offset=50) # Create the import - do_import = ImportFrom(module=mod, names=[alias(name=base_class, asname=None)], level=1) + # Put it in the decorator line. + do_import = ImportFrom(module=mod, names=[alias(name=base_class, asname=None)], level=1, + lineno=l_start-1, col_offset=0, end_lineno=l_start-1, end_col_offset=50) return [do_import, tree, do_register] # Just in case somebody applies it to anything other than a class diff --git a/kibot/mcpy/visitors.py b/kibot/mcpy/visitors.py index 8095b90e..22d73e56 100644 --- a/kibot/mcpy/visitors.py +++ b/kibot/mcpy/visitors.py @@ -1,5 +1,5 @@ # from functools import wraps -from ast import NodeTransformer, AST, copy_location, fix_missing_locations +from ast import NodeTransformer, AST, copy_location, fix_missing_locations, Call, Constant, Name, Expr, Load from .unparse import unparse @@ -32,7 +32,25 @@ class BaseMacroExpander(NodeTransformer): }) expansion = _apply_macro(macro, tree, kw) - return self._visit_expansion(expansion, target) + if syntax == 'block': + # I'm not sure why is all this mess + # + # Strategy 1: Make the last line cover the whole block. + # Result: Covers the "with document" line, but not the last one. + # copy_location(expansion[-1], target) + # + # Strategy 2: Make the second line cover the whole block. + # Result: Covers all, unless the block is just 2 lines. + # copy_location(expansion[1], target) # Lo mejor para largo > 2 + # + # Strategy 3: Insert a second dummy line covering the whole block. + # Result: Works + dummy = Expr(value=Call(func=Name(id="id", ctx=Load()), args=[Constant(value="bogus", kind=None)], keywords=[]), + lineno=target.lineno) + copy_location(dummy, target) + expansion.insert(1, dummy) + expansion = self._visit_expansion(expansion, target) + return expansion def _visit_expansion(self, expansion, target): """ @@ -42,7 +60,10 @@ class BaseMacroExpander(NodeTransformer): if expansion is not None: is_node = isinstance(expansion, AST) expansion = [expansion] if is_node else expansion - expansion = map(lambda n: copy_location(n, target), expansion) + # The following is nice when we don't put any effort in filling lineno, but then is impossible to get a + # rasonable coverage. So now I'm disabling it and doing some extra effort to indicate where is the code. + # expansion = map(lambda n: copy_location(n, target), expansion) + # The following fills the gaps expansion = map(fix_missing_locations, expansion) expansion = map(self.visit, expansion) expansion = list(expansion).pop() if is_node else list(expansion) diff --git a/kibot/out_any_drill.py b/kibot/out_any_drill.py index b2e982d2..d74451db 100644 --- a/kibot/out_any_drill.py +++ b/kibot/out_any_drill.py @@ -20,7 +20,7 @@ class DrillMap(Optionable): self.output = GS.def_global_output """ name for the map file, KiCad defaults if empty (%i='PTH_drill_map') """ self.type = 'pdf' - """ [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map """ # pragma: no cover + """ [hpgl,ps,gerber,dxf,svg,pdf] format for a graphical drill map """ super().__init__() self._unkown_is_error = True @@ -31,7 +31,7 @@ class DrillReport(Optionable): with document: self.filename = '' """ name of the drill report. Not generated unless a name is specified. - (%i='drill_report' %x='txt') """ # pragma: no cover + (%i='drill_report' %x='txt') """ self._unkown_is_error = True @@ -47,7 +47,7 @@ class AnyDrill(BaseOptions): self.output = GS.def_global_output """ name for the drill file, KiCad defaults if empty (%i='PTH_drill') """ self.report = DrillReport - """ [dict|string] name of the drill report. Not generated unless a name is specified """ # pragma: no cover + """ [dict|string] name of the drill report. Not generated unless a name is specified """ super().__init__() # Mappings to KiCad values self._map_map = { diff --git a/kibot/out_any_layer.py b/kibot/out_any_layer.py index f98eeda2..55058747 100644 --- a/kibot/out_any_layer.py +++ b/kibot/out_any_layer.py @@ -37,7 +37,7 @@ class AnyLayerOptions(BaseOptions): self.output = GS.def_global_output """ output file name, the default KiCad name if empty """ self.tent_vias = True - """ cover the vias """ # pragma: no cover + """ cover the vias """ super().__init__() def _configure_plot_ctrl(self, po, output_dir): @@ -128,7 +128,7 @@ class AnyLayer(BaseOutput): with document: self.layers = Layer """ [list(dict)|list(string)|string] [all,selected,copper,technical,user] - List of PCB layers to plot """ # pragma: no cover + List of PCB layers to plot """ def config(self): super().config() diff --git a/kibot/out_bom.py b/kibot/out_bom.py index 0172d871..81557193 100644 --- a/kibot/out_bom.py +++ b/kibot/out_bom.py @@ -42,7 +42,7 @@ class BoMRegex(Optionable): self.field = None """ {column} """ self.regexp = None - """ {regex} """ # pragma: no cover + """ {regex} """ # def __str__(self): # return self.column+'\t'+self.regex @@ -59,7 +59,7 @@ class BoMColumns(Optionable): self.name = '' """ Name to display in the header. The field is used when empty """ self.join = Optionable - """ [list(string)|string=''] List of fields to join to this column """ # pragma: no cover + """ [list(string)|string=''] List of fields to join to this column """ def config(self): super().config() @@ -101,7 +101,7 @@ class BoMLinkable(Optionable): self.logo = Optionable """ [string|boolean=''] PNG file to use as logo, use false to remove """ self.title = 'KiBot Bill of Materials' - """ BoM title """ # pragma: no cover + """ BoM title """ def config(self): super().config() @@ -130,7 +130,7 @@ class BoMHTML(BoMLinkable): with document: self.style = 'modern-blue' """ Page style. Internal styles: modern-blue, modern-green, modern-red and classic. - Or you can provide a CSS file name. Please use .css as file extension. """ # pragma: no cover + Or you can provide a CSS file name. Please use .css as file extension. """ def config(self): super().config() @@ -155,7 +155,7 @@ class BoMCSV(Optionable): self.hide_stats_info = False """ Hide statistics information """ self.quote_all = False - """ Enclose all values using double quotes """ # pragma: no cover + """ Enclose all values using double quotes """ class BoMXLSX(BoMLinkable): @@ -166,7 +166,7 @@ class BoMXLSX(BoMLinkable): self.max_col_width = 60 """ [20,999] Maximum column width (characters) """ self.style = 'modern-blue' - """ Head style: modern-blue, modern-green, modern-red and classic. """ # pragma: no cover + """ Head style: modern-blue, modern-green, modern-red and classic. """ def config(self): super().config() @@ -285,7 +285,7 @@ class BoMOptions(BaseOptions): self.xlsx = BoMXLSX """ [dict] Options for the XLSX format """ self.csv = BoMCSV - """ [dict] Options for the CSV, TXT and TSV formats """ # pragma: no cover + """ [dict] Options for the CSV, TXT and TSV formats """ super().__init__() @staticmethod @@ -449,5 +449,5 @@ class BoM(BaseOutput): # noqa: F821 super().__init__() with document: self.options = BoMOptions - """ [dict] Options for the `bom` output """ # pragma: no cover + """ [dict] Options for the `bom` output """ self._sch_related = True diff --git a/kibot/out_dxf.py b/kibot/out_dxf.py index e40b67f4..44248f5b 100644 --- a/kibot/out_dxf.py +++ b/kibot/out_dxf.py @@ -20,7 +20,7 @@ class DXFOptions(DrillMarks): self.metric_units = False """ use mm instead of inches """ self.sketch_plot = False - """ don't fill objects, just draw the outline """ # pragma: no cover + """ don't fill objects, just draw the outline """ self._plot_format = PLOT_FORMAT_DXF def _configure_plot_ctrl(self, po, output_dir): @@ -50,4 +50,4 @@ class DXF(AnyLayer): super().__init__() with document: self.options = DXFOptions - """ [dict] Options for the `dxf` output """ # pragma: no cover + """ [dict] Options for the `dxf` output """ diff --git a/kibot/out_excellon.py b/kibot/out_excellon.py index 27959cd1..cc8f90e9 100644 --- a/kibot/out_excellon.py +++ b/kibot/out_excellon.py @@ -19,7 +19,7 @@ class ExcellonOptions(AnyDrill): self.minimal_header = False """ use a minimal header in the file """ self.mirror_y_axis = False - """ invert the Y axis """ # pragma: no cover + """ invert the Y axis """ def _configure_writer(self, board, offset): drill_writer = EXCELLON_WRITER(board) @@ -38,4 +38,4 @@ class Excellon(BaseOutput): # noqa: F821 super().__init__() with document: self.options = ExcellonOptions - """ [dict] Options for the `excellon` output """ # pragma: no cover + """ [dict] Options for the `excellon` output """ diff --git a/kibot/out_gerb_drill.py b/kibot/out_gerb_drill.py index d3a62774..deb06527 100644 --- a/kibot/out_gerb_drill.py +++ b/kibot/out_gerb_drill.py @@ -30,4 +30,4 @@ class Gerb_Drill(BaseOutput): # noqa: F821 super().__init__() with document: self.options = Gerb_DrillOptions - """ [dict] Options for the `gerb_drill` output """ # pragma: no cover + """ [dict] Options for the `gerb_drill` output """ diff --git a/kibot/out_gerber.py b/kibot/out_gerber.py index ab333e28..edac71e6 100644 --- a/kibot/out_gerber.py +++ b/kibot/out_gerber.py @@ -34,7 +34,7 @@ class GerberOptions(AnyLayerOptions): self.use_gerber_x2_attributes = True """ use the extended X2 format """ self.use_gerber_net_attributes = True - """ include netlist metadata """ # pragma: no cover + """ include netlist metadata """ @property def gerber_precision(self): @@ -87,4 +87,4 @@ class Gerber(AnyLayer): super().__init__() with document: self.options = GerberOptions - """ [dict] Options for the `gerber` output """ # pragma: no cover + """ [dict] Options for the `gerber` output """ diff --git a/kibot/out_hpgl.py b/kibot/out_hpgl.py index 11ab5723..0b390395 100644 --- a/kibot/out_hpgl.py +++ b/kibot/out_hpgl.py @@ -25,7 +25,7 @@ class HPGLOptions(DrillMarks): self.pen_speed = 20 """ [1,99] pen speed """ self.pen_width = 15 - """ [0,100] pen diameter in MILS, useful to fill areas. However, it is in mm in HPGL files """ # pragma: no cover + """ [0,100] pen diameter in MILS, useful to fill areas. However, it is in mm in HPGL files """ self._plot_format = PLOT_FORMAT_HPGL def _configure_plot_ctrl(self, po, output_dir): @@ -65,4 +65,4 @@ class HPGL(AnyLayer): super().__init__() with document: self.options = HPGLOptions - """ [dict] Options for the `hpgl` output """ # pragma: no cover + """ [dict] Options for the `hpgl` output """ diff --git a/kibot/out_ibom.py b/kibot/out_ibom.py index 06437762..08f74764 100644 --- a/kibot/out_ibom.py +++ b/kibot/out_ibom.py @@ -72,7 +72,7 @@ class IBoMOptions(BaseOptions): """ List of board variants to exclude from the BOM """ self.dnp_field = '' """ Name of the extra field that indicates do not populate status. Components with this field not empty will be - blacklisted """ # pragma: no cover + blacklisted """ super().__init__() def run(self, output_dir, board): @@ -123,4 +123,4 @@ class IBoM(BaseOutput): # noqa: F821 super().__init__() with document: self.options = IBoMOptions - """ [dict] Options for the `ibom` output """ # pragma: no cover + """ [dict] Options for the `ibom` output """ diff --git a/kibot/out_kibom.py b/kibot/out_kibom.py index 2573785f..098dfe36 100644 --- a/kibot/out_kibom.py +++ b/kibot/out_kibom.py @@ -330,7 +330,7 @@ class KiBoMOptions(BaseOptions): self.output = GS.def_global_output """ filename for the output (%i=bom)""" self.format = 'HTML' - """ [HTML,CSV,XML,XLSX] format for the BoM """ # pragma: no cover + """ [HTML,CSV,XML,XLSX] format for the BoM """ super().__init__() def config(self): @@ -397,5 +397,5 @@ class KiBoM(BaseOutput): # noqa: F821 super().__init__() with document: self.options = KiBoMOptions - """ [dict] Options for the `kibom` output """ # pragma: no cover + """ [dict] Options for the `kibom` output """ self._sch_related = True diff --git a/kibot/out_pcbdraw.py b/kibot/out_pcbdraw.py index a7b6545e..fe45e63d 100644 --- a/kibot/out_pcbdraw.py +++ b/kibot/out_pcbdraw.py @@ -42,7 +42,7 @@ class PcbDrawStyle(Optionable): self.highlight_style = "stroke:none;fill:#ff0000;opacity:0.5;" """ SVG code for the highlight style """ self.highlight_padding = 1.5 - """ [0,1000] how much the highlight extends around the component [mm] """ # pragma: no cover + """ [0,1000] how much the highlight extends around the component [mm] """ def validate_color(self, name): color = getattr(self, name) @@ -107,7 +107,7 @@ class PcbDrawOptions(BaseOptions): self.format = 'svg' """ [svg,png,jpg] output format. Only used if no `output` is specified """ self.output = GS.def_global_output - """ name for the generated file """ # pragma: no cover + """ name for the generated file """ super().__init__() def config(self): @@ -246,4 +246,4 @@ class PcbDraw(BaseOutput): # noqa: F821 super().__init__() with document: self.options = PcbDrawOptions - """ [dict] Options for the `pcbdraw` output """ # pragma: no cover + """ [dict] Options for the `pcbdraw` output """ diff --git a/kibot/out_pdf.py b/kibot/out_pdf.py index 7fd47eb4..76e16845 100644 --- a/kibot/out_pdf.py +++ b/kibot/out_pdf.py @@ -23,7 +23,7 @@ class PDFOptions(DrillMarks): self.mirror_plot = False """ plot mirrored """ self.negative_plot = False - """ invert black and white """ # pragma: no cover + """ invert black and white """ self._plot_format = PLOT_FORMAT_PDF def _configure_plot_ctrl(self, po, output_dir): @@ -49,4 +49,4 @@ class PDF(AnyLayer, DrillMarks): super().__init__() with document: self.options = PDFOptions - """ [dict] Options for the `pdf` output """ # pragma: no cover + """ [dict] Options for the `pdf` output """ diff --git a/kibot/out_pdf_pcb_print.py b/kibot/out_pdf_pcb_print.py index 7ad7aeb7..314bb222 100644 --- a/kibot/out_pdf_pcb_print.py +++ b/kibot/out_pdf_pcb_print.py @@ -22,7 +22,7 @@ class PDF_Pcb_PrintOptions(BaseOptions): self.output = GS.def_global_output """ filename for the output PDF (%i=layers, %x=pdf)""" self.output_name = None - """ {output} """ # pragma: no cover + """ {output} """ super().__init__() def run(self, output_dir, board, layers): @@ -62,7 +62,7 @@ class PDF_Pcb_Print(BaseOutput): # noqa: F821 """ [dict] Options for the `pdf_pcb_print` output """ self.layers = Layer """ [list(dict)|list(string)|string] [all,selected,copper,technical,user] - List of PCB layers to include in the PDF """ # pragma: no cover + List of PCB layers to include in the PDF """ def config(self): super().config() diff --git a/kibot/out_pdf_sch_print.py b/kibot/out_pdf_sch_print.py index 49300aaa..6c1ae014 100644 --- a/kibot/out_pdf_sch_print.py +++ b/kibot/out_pdf_sch_print.py @@ -18,7 +18,7 @@ class PDF_Sch_PrintOptions(BaseOptions): def __init__(self): with document: self.output = GS.def_global_output - """ filename for the output PDF (%i=schematic %x=pdf) """ # pragma: no cover + """ filename for the output PDF (%i=schematic %x=pdf) """ super().__init__() def run(self, output_dir, board): @@ -50,5 +50,5 @@ class PDF_Sch_Print(BaseOutput): # noqa: F821 super().__init__() with document: self.options = PDF_Sch_PrintOptions - """ [dict] Options for the `pdf_sch_print` output """ # pragma: no cover + """ [dict] Options for the `pdf_sch_print` output """ self._sch_related = True diff --git a/kibot/out_position.py b/kibot/out_position.py index 5fb87731..8044cf4d 100644 --- a/kibot/out_position.py +++ b/kibot/out_position.py @@ -25,7 +25,7 @@ class PositionOptions(BaseOptions): self.output = GS.def_global_output """ output file name (%i='top_pos'|'bottom_pos'|'both_pos', %x='pos'|'csv') """ self.units = 'millimeters' - """ [millimeters,inches] units used for the positions """ # pragma: no cover + """ [millimeters,inches] units used for the positions """ super().__init__() def _do_position_plot_ascii(self, board, output_dir, columns, modulesStr, maxSizes): @@ -167,4 +167,4 @@ class Position(BaseOutput): # noqa: F821 super().__init__() with document: self.options = PositionOptions - """ [dict] Options for the `position` output """ # pragma: no cover + """ [dict] Options for the `position` output """ diff --git a/kibot/out_ps.py b/kibot/out_ps.py index 5caaf8a8..1e88d36b 100644 --- a/kibot/out_ps.py +++ b/kibot/out_ps.py @@ -34,7 +34,7 @@ class PSOptions(DrillMarks): """ this width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings. Only used to plot pads and tracks """ self.a4_output = True - """ force A4 paper size """ # pragma: no cover + """ force A4 paper size """ self._plot_format = PLOT_FORMAT_POST def _configure_plot_ctrl(self, po, output_dir): @@ -80,4 +80,4 @@ class PS(AnyLayer): super().__init__() with document: self.options = PSOptions - """ [dict] Options for the `ps` output """ # pragma: no cover + """ [dict] Options for the `ps` output """ diff --git a/kibot/out_step.py b/kibot/out_step.py index d259c027..7ede8753 100644 --- a/kibot/out_step.py +++ b/kibot/out_step.py @@ -29,7 +29,7 @@ class STEPOptions(BaseOptions): self.min_distance = -1 """ the minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm) """ self.output = GS.def_global_output - """ name for the generated STEP file (%i='3D' %x='step') """ # pragma: no cover + """ name for the generated STEP file (%i='3D' %x='step') """ super().__init__() @property @@ -89,4 +89,4 @@ class STEP(BaseOutput): # noqa: F821 super().__init__() with document: self.options = STEPOptions - """ [dict] Options for the `step` output """ # pragma: no cover + """ [dict] Options for the `step` output """ diff --git a/kibot/out_svg.py b/kibot/out_svg.py index 347c4628..a02f59da 100644 --- a/kibot/out_svg.py +++ b/kibot/out_svg.py @@ -20,7 +20,7 @@ class SVGOptions(DrillMarks): self.mirror_plot = False """ plot mirrored """ self.negative_plot = False - """ invert black and white """ # pragma: no cover + """ invert black and white """ self._plot_format = PLOT_FORMAT_SVG def _configure_plot_ctrl(self, po, output_dir): @@ -46,4 +46,4 @@ class SVG(AnyLayer): super().__init__() with document: self.options = SVGOptions - """ [dict] Options for the `svg` output """ # pragma: no cover + """ [dict] Options for the `svg` output """ diff --git a/kibot/out_svg_sch_print.py b/kibot/out_svg_sch_print.py index 16d5f02d..dfd97e37 100644 --- a/kibot/out_svg_sch_print.py +++ b/kibot/out_svg_sch_print.py @@ -19,7 +19,7 @@ class SVG_Sch_PrintOptions(BaseOptions): def __init__(self): with document: self.output = GS.def_global_output - """ filename for the output SVG (%i=schematic %x=svg) """ # pragma: no cover + """ filename for the output SVG (%i=schematic %x=svg) """ super().__init__() def run(self, output_dir, board): @@ -50,5 +50,5 @@ class SVG_Sch_Print(BaseOutput): # noqa: F821 super().__init__() with document: self.options = SVG_Sch_PrintOptions - """ [dict] Options for the `svg_sch_print` output """ # pragma: no cover + """ [dict] Options for the `svg_sch_print` output """ self._sch_related = True diff --git a/kibot/pre_filters.py b/kibot/pre_filters.py index 2ff6a546..f6dcd308 100644 --- a/kibot/pre_filters.py +++ b/kibot/pre_filters.py @@ -31,7 +31,7 @@ class FilterOptions(Optionable): self.regex = 'None' """ Regular expression to match the text for the error we want to exclude """ self.regexp = None - """ {regex} """ # pragma: no cover + """ {regex} """ class FiltersOptions(Optionable): @@ -40,7 +40,7 @@ class FiltersOptions(Optionable): super().__init__() with document: self.filters = FilterOptions - """ [list(dict)] DRC/ERC errors to be ignored """ # pragma: no cover + """ [list(dict)] DRC/ERC errors to be ignored """ def config(self): super().config()