Modified mcpy to better report the line numbers.

Seems to work for Python 3.8, but I have doubts for 3.7.
This commit is contained in:
SET 2020-08-19 19:31:46 -03:00
parent 28947ed70d
commit 93c824b083
24 changed files with 105 additions and 57 deletions

View File

@ -8,7 +8,7 @@ omit = */kibot/docopt.py
[report]
exclude_lines =
pragma: no cover
@pre_class
@output_class
# @pre_class
# @output_class
# raise RuntimeError

View File

@ -49,13 +49,27 @@ 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 ; chown -R $(USER_ID):$(GROUP_ID) output/ tests/board_samples/ .coverage"
$(PY_COV) report
$(PY_COV) html
x-www-browser htmlcov/index.html

View File

@ -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,13 @@ 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_location(sentences[n], s)
# else:
# if isinstance(s, Expr):
# print(s.__dict__)
# print(s.value.__dict__)
# print(s.value.func.__dict__)
# print(s.value.args[0].__dict__)
prev = s
# Return the modified AST
return sentences
@ -86,14 +93,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
if hasattr(tree, 'end_lineno'):
l_end = tree.end_lineno
else:
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

View File

@ -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,9 +32,24 @@ class BaseMacroExpander(NodeTransformer):
})
expansion = _apply_macro(macro, tree, kw)
return self._visit_expansion(expansion, target)
if syntax == 'block':
# pass
# The best I can do
# If I keep the real line numbers the "with ..." isn't "covered"
# copy_location(expansion[-1], target) # Esto cubre la 1ra pero nunca la última
# copy_location(expansion[1], target) # Lo mejor para largo > 2
# Esto parece funcionar, pero puede tener efectos secundarios
# dummy = deepcopy(expansion[1])
# copy_location(dummy, target)
# expansion.insert(1, dummy)
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, syntax)
return expansion
def _visit_expansion(self, expansion, target):
def _visit_expansion(self, expansion, target, syntax):
"""
Ensures the macro expansions into None (deletions), other nodes or
list of nodes are expanded too.
@ -42,7 +57,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)

View File

@ -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 = {

View File

@ -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()

View File

@ -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

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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

View File

@ -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 """

View File

@ -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 """

View File

@ -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()

View File

@ -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

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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 """

View File

@ -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

View File

@ -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()