Improve built-in pylint test.

- All pylint messages are collected and emitted at the end.
- Always run pylint with default settings (unless the file is marked with 'skip-file').
  Filtering the messages happens later.
- Files with lots of pylint issues are marked as 'pylint-unclean'
  - pylint only complains about whitelisted issues for these files (unless --pedantic=false).
  - fail the test if a file that is marked as unclean turns out to not have many issues after all.

- And finally: fix all issues reported in --pedantic=false mode.
looper
Ben Niemann 5 years ago
parent 9cf494ba62
commit 06d0b7ce4d

@ -1,15 +1,20 @@
# -*- org-tags-column: -98 -*-
* Builtin pylint test improvements :CLEANUP:
- mark all files currently with pylint issues with a "# TODO: pylint-unclean" string
- test reads first 1K bytes and checks for that string
- with --pedantic=false:
- run pylint-unclean files with only whitelisted messages
- run pylint-clean files with default settings
- with --pedantic=true:
- raise error, if file has not messages reported, but is tagges as pylint-unclean
- do not autogenerated modules (*_pb2)
- look for some easy to fix issues, fix them everywhere and add to whitelist
* pylint doesn't work with cython/proto modules :BUG:
- throws import-error or no-name-in-modules errors.
- could it be that pylint is looking in the src, not in the build dir?
* clean up pylint issues in pylint-unclean files :CLEANUP:
- grep -r pylint-unclean noisicaa/
- pick some file and clean it up.
- until grep finds no more files.
* runtests should run tests in parallel :FR:
- using subprocesses to isolate test cases from each other.
* remove some cruft :CLEANUP:
- noisicaa.ui.command_shell (nice idea, never used)
- noisicaa.audio_playground (obsolete)
* Handle async calls using a "queue pump" :CLEANUP:
- separate class

@ -79,7 +79,7 @@ confidence=
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,missing-docstring,invalid-name,no-self-use,locally-disabled,star-args,R
disable=fixme,missing-docstring,invalid-name,no-self-use,locally-disabled,star-args,len-as-condition,unused-argument,R
#W1604,W1603,W1610,W1614,E1606,W1626,W1609,W0704,W1630,W1601,W1623,W1602,W1631,E1604,W1616,W1611,W1628,W1607,E1602,W1625,W1622,W1612,I0020,E1605,E1608,W1619,W1620,W1617,W1632,W1624,W1613,W1615,E1607,E1601,E1603,W1608,W1629,W1627,W1606,I0021,W1605,W1633,W1618,W1621
@ -206,7 +206,7 @@ bad-functions=map,filter
good-names=i,j,k,ex,Run,_
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
bad-names=foo,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.

@ -1,3 +1,5 @@
#!/usr/bin/python3
# @begin:license
#
# Copyright (c) 2015-2018, Benjamin Niemann <pink@odahoda.de>
@ -17,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import sys
import argparse

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import functools
import logging
import sys

@ -39,8 +39,7 @@ class AddNode(Mutation):
return '<AddNode name="%s" type=%s%s>' % (
self.description.display_name,
type(self.description).__name__,
''.join(' %s=%r' % (k, v)
for k, v in sorted(self.args.items())))
''.join(' %s=%r' % (k, v) for k, v in sorted(self.args.items())))
class RemoveNode(Mutation):

@ -36,7 +36,10 @@ class Node(object):
init_ports_from_description = True
init_parameters_from_description = True
def __init__(self, *, host_data, description, id, name=None, initial_parameters=None):
def __init__(
self, *,
host_data, description, id, # pylint: disable=redefined-builtin
name=None, initial_parameters=None):
assert isinstance(description, node_db.NodeDescription), description
self._host_data = host_data

@ -20,6 +20,7 @@
#
# @end:license
class NodeDB(object):
def __init__(self):
self._db = {}
@ -32,4 +33,3 @@ class NodeDB(object):
def create(self, cls_name, host_data, **args):
cls = self._db[cls_name]
return cls(host_data=host_data, **args)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
from . import vm

@ -18,17 +18,18 @@
#
# @end:license
from .musical_time import (
# TODO: pylint has issues with cython and proto modules.
from .musical_time import ( # pylint: disable=import-error
PyMusicalDuration as MusicalDuration,
PyMusicalTime as MusicalTime,
)
from .time_mapper import (
from .time_mapper import ( # pylint: disable=import-error
PyTimeMapper as TimeMapper,
)
from .player_state_pb2 import (
from .player_state_pb2 import ( # pylint: disable=import-error
PlayerState,
)
from .processor_message_pb2 import (
from .processor_message_pb2 import ( # pylint: disable=import-error
ProcessorMessage,
ProcessorMessageList,
)

@ -18,38 +18,41 @@
#
# @end:license
from .engine import PipelineVM
# TODO: pylint has issues with cython modules.
from .engine import PipelineVM # pylint: disable=import-error
from .compiler import compile_graph
from .audio_stream import (
from .audio_stream import ( # pylint: disable=import-error
AudioStream,
)
from .block_data_capnp import (
# pylint doesn't know about capnp import magic
import capnp # pylint: disable=unused-import,wrong-import-order
from .block_data_capnp import ( # pylint: disable=import-error
BlockData,
Buffer,
)
from .spec import (
from .spec import ( # pylint: disable=import-error
PySpec as Spec
)
from .buffers import (
from .buffers import ( # pylint: disable=import-error
PyBufferType as BufferType,
PyFloat as Float,
PyFloatAudioBlock as FloatAudioBlock,
PyAtomData as AtomData,
)
from .control_value import (
from .control_value import ( # pylint: disable=import-error
PyControlValueType as ControlValueType,
PyFloatControlValue as FloatControlValue,
PyIntControlValue as IntControlValue,
)
from .host_data import (
from .host_data import ( # pylint: disable=import-error
PyHostData as HostData,
)
from .block_context import (
from .block_context import ( # pylint: disable=import-error
PyBlockContext as BlockContext,
)
from .processor import (
from .processor import ( # pylint: disable=import-error
PyProcessor as Processor,
)
from .processor_spec import (
from .processor_spec import ( # pylint: disable=import-error
PyProcessorSpec as ProcessorSpec,
)

@ -25,7 +25,8 @@ import threading
from noisidev import unittest
from noisicaa.constants import TEST_OPTS
from . import audio_stream
# TODO: pylint has issues with cython modules.
from . import audio_stream # pylint: disable=no-name-in-module
class TestAudioStream(unittest.TestCase):

@ -25,11 +25,12 @@ import struct
from noisidev import unittest
from noisicaa import constants
from . import engine
from .spec import PySpec
from .buffers import PyFloat, PyFloatAudioBlock
from .block_context import PyBlockContext
from .host_data import PyHostData
# TODO: pylint has issues with cython modules.
from . import engine # pylint: disable=no-name-in-module
from .spec import PySpec # pylint: disable=import-error
from .buffers import PyFloat, PyFloatAudioBlock # pylint: disable=import-error
from .block_context import PyBlockContext # pylint: disable=import-error
from .host_data import PyHostData # pylint: disable=import-error
logger = logging.getLogger(__name__)

@ -19,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -18,11 +18,12 @@
#
# @end:license
from .urid import (
# TODO: pylint has issues with cython modules.
from .urid import ( # pylint: disable=import-error
DynamicURIDMapper,
static_mapper,
)
from .atom import (
from .atom import ( # pylint: disable=import-error
AtomForge,
wrap_atom,
)

@ -21,9 +21,10 @@
import logging
from noisidev import unittest
from noisicaa.bindings import sratom
from . import atom
from . import urid
# TODO: pylint has issues with cython modules.
from noisicaa.bindings import sratom # pylint: disable=no-name-in-module
from . import atom # pylint: disable=no-name-in-module
from . import urid # pylint: disable=no-name-in-module
logger = logging.getLogger(__name__)

@ -25,7 +25,8 @@ import os.path
import numpy
from noisidev import unittest
from . import sndfile
# TODO: pylint has issues with cython modules.
from . import sndfile # pylint: disable=no-name-in-module
class SndFileTest(unittest.TestCase):
@ -41,5 +42,5 @@ class SndFileTest(unittest.TestCase):
with sndfile.SndFile(os.path.join(unittest.TESTDATA_DIR, 'test1.wav')) as sf:
smpls = sf.get_samples()
self.assertIsInstance(smpls, numpy.ndarray)
self.assertEqual(smpls.dtype, numpy.float32)
self.assertEqual(smpls.dtype, numpy.float32) # pylint: disable=no-member
self.assertEqual(smpls.shape, (9450, 2))

@ -22,7 +22,8 @@
from noisidev import unittest
from . import lv2
from . import sratom
# TODO: pylint has issues with cython modules.
from . import sratom # pylint: disable=no-name-in-module
class SratomTest(unittest.TestCase):
@ -37,4 +38,3 @@ class SratomTest(unittest.TestCase):
turtle = sratom.atom_to_turtle(lv2.static_mapper, buf)
self.assertIsInstance(turtle, str)

@ -42,7 +42,7 @@ def __xdg_user_dir(resource):
try:
res = subprocess.run(['/usr/bin/xdg-user-dir', resource], stdout=subprocess.PIPE)
return os.fsdecode(res.stdout.rstrip(b'\n'))
except:
except: # pylint: disable=bare-except
return os.path.expanduser('~')
MUSIC_DIR = __xdg_user_dir('MUSIC')

@ -18,6 +18,7 @@
#
# @end:license
# TODO: pylint doesn't handle cython modules correctly
from .model_base import (
ObjectBase,
@ -40,7 +41,7 @@ from .process_manager import (
from .callbacks import (
CallbackRegistry,
)
from .perf_stats import (
from .perf_stats import ( # pylint: disable=import-error
PyPerfStats as PerfStats,
)
from .message import (
@ -49,10 +50,10 @@ from .message import (
MessageType,
MessageKey,
)
from .logging import (
from .logging import ( # pylint: disable=import-error
init_pylogging,
)
from .status import (
from .status import ( # pylint: disable=import-error
Error,
ConnectionClosed,
)

@ -316,8 +316,8 @@ class LogFile(object):
class MimeLogFile(LogFile):
def append(self, content, entry_type, content_type,
encoding='utf-8', headers=None):
def append( # pylint: disable=arguments-differ
self, content, entry_type, content_type, encoding='utf-8', headers=None):
policy = email.policy.compat32.clone(
linesep='\n',
max_line_length=0,

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import enum
import functools

@ -22,9 +22,9 @@
import logging
# pylint doesn't know about the capnp import magic.
import capnp # pylint: disable=unused-import
from . import message_capnp
from . import message_capnp # pylint: disable=no-name-in-module
logger = logging.getLogger(__name__)
@ -41,7 +41,7 @@ def build_labelset(labels):
label.value = v
return lset
def build_message(labels, type, data):
def build_message(labels, type, data): # pylint: disable=redefined-builtin
msg = message_capnp.Message.new_message()
msg.init('labelset')
msg.labelset.init('labels', len(labels))

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
logger = logging.getLogger(__name__)

@ -20,8 +20,10 @@
#
# @end:license
# TODO: pylint doesn't handle cython modules correctly
from noisidev import unittest
from . import perf_stats
from . import perf_stats # pylint: disable=no-name-in-module
class TestPerfStats(perf_stats.PyPerfStats):
def __init__(self):

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import enum
import functools

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import os
import signal

@ -45,7 +45,7 @@ class Builder(object):
def compile_expression(expr):
try:
builder = eval(expr, {'SELECT': Builder})
builder = eval(expr, {'SELECT': Builder}) # pylint: disable=eval-used
except Exception as exc:
raise InvalidExpressionError(str(exc))
return builder.get_code()

@ -65,8 +65,9 @@ class StatName(object):
return True
def merge(self, other):
assert isinstance(other, StatName)
labels = self.__labels.copy()
labels.update(other.__labels)
labels.update(other.__labels) # pylint: disable=protected-access
return StatName(**labels)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import collections
import logging
import os

@ -20,4 +20,6 @@
from .midi_hub import MidiHub
from .midi_events import MidiEvent, NoteOnEvent, NoteOffEvent, ControlChangeEvent
from .libalsa import AlsaSequencer, PortInfo, ClientInfo
# TODO: pylint has issues with cython modules
from .libalsa import AlsaSequencer, PortInfo, ClientInfo # pylint: disable=import-error

@ -23,7 +23,9 @@
import time
from noisidev import unittest
from . import libalsa
# TODO: pylint has issues with cython modules
from . import libalsa # pylint: disable=no-name-in-module
class AlsaSequencerTest(unittest.TestCase):
def test_list_clients(self):

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
class MidiEvent(object):
NOTE_ON = 'note-on'
NOTE_OFF = 'note-off'

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import argparse
import logging
import select
@ -169,4 +171,3 @@ if __name__ == '__main__':
except KeyboardInterrupt:
print("***BREAK")
listener.remove()

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import threading
from unittest import mock

@ -19,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -21,7 +21,6 @@
# @end:license
import logging
import os.path
import re
import fractions
import collections
@ -97,7 +96,7 @@ class ABCImporter(object):
elif self.state == 'tune':
if line.strip() == '':
self.state == 'after-tune'
self.state = 'after-tune'
if self.state == 'start':
if line.strip() != '':
@ -115,12 +114,10 @@ class ABCImporter(object):
else:
self.parse_music(line)
sheet = music.Sheet(name=os.path.basename(path), num_tracks=0)
for voice in self.voices.values():
sheet.tracks.append(voice)
sheet.equalize_tracks()
sheet.bpm = self.tempo
proj.sheets.append(sheet)
proj.master_group.tracks.append(voice)
proj.equalize_tracks()
proj.bpm = self.tempo
def parse_information_field(self, field, contents):
if field == 'K':

@ -92,13 +92,12 @@ class ABCImporterTest(unittest.TestCase):
def test_pitch_in_key(self):
self.imp.unit_length = fractions.Fraction(1, 4)
self.imp.key = music.KeySignature('G major')
note, remainder = self.imp.parse_note('f')
note, _ = self.imp.parse_note('f')
self.assertEqual(str(note), 'F#5/4')
def test_broken_rhythm(self):
self.imp.unit_length = fractions.Fraction(1, 4)
self.imp.parse_music('a>b c<d')
notes = [str(n) for n in self.imp.notes]
self.assertEqual(
[str(n) for n in self.imp.notes],
['A5;3/8', 'B5/8', 'C5/8', 'D5;3/8'])

@ -19,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -93,7 +93,7 @@ class SoundFont(riff.RiffFile):
handler_name = 'handle_' + '_'.join(path + [identifier])
handler = getattr(self, handler_name, None)
if handler is not None:
handler(size, fp)
handler(size, fp) # pylint: disable=not-callable
def handle_sfbk_INFO_ifil(self, size, fp):
if size != 4:

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
import struct

@ -20,6 +20,7 @@
#
# @end:license
class Mutation(object):
pass
@ -38,5 +39,3 @@ class RemoveInstrumentDescription(Mutation):
def __str__(self):
return '<RemoveInstrumentDescription uri="%s">' % self.uri

@ -19,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import os
import os.path
import logging

@ -35,6 +35,5 @@ class Scanner(object):
urllib.parse.urlencode(sorted((k, str(v)) for k, v in kwargs.items()), True),
None))
def scan(self):
def scan(self, path):
raise NotImplementedError

@ -35,7 +35,8 @@ from . import process_base
logger = logging.getLogger(__name__)
class InvalidSessionError(Exception): pass
class InvalidSessionError(Exception):
pass
class Session(object):

@ -42,6 +42,8 @@ from .property_track import (
from .misc import (
Pos2F
)
from .render_settings_pb2 import (
# TODO: pylint has problems with proto imports.
from .render_settings_pb2 import ( # pylint: disable=import-error
RenderSettings,
)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
import random

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import functools
import logging

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import contextlib
import logging

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
from noisidev import unittest
from noisicaa import core
from . import commands

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
import random

@ -40,7 +40,7 @@ class NodeDB(object):
self.db.cleanup()
def get_node_description(self, uri):
return self.db._nodes[uri]
return self.db._nodes[uri] # pylint: disable=protected-access
class ControlTrackConnectorTest(unittest.AsyncTestCase):

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
class Pos2F(object):
def __init__(self, x, y):
self._x = float(x)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import fractions
from noisicaa import core

@ -119,5 +119,3 @@ class ListDelete(Mutation):
def __str__(self):
return '<ListDelete id=%s prop=%s index=%d>' % (
self.id, self.prop_name, self.index)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import io
import logging
from xml.etree import ElementTree

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import re
NOTE_TO_MIDI = {}

@ -483,7 +483,8 @@ class Player(object):
def add_track(self, track):
for t in track.walk_tracks(groups=True, tracks=True):
if isinstance(t, model.TrackGroup):
self.__listeners['track_group:%s' % t.id] = t.listeners.add('tracks', self.tracks_changed)
self.__listeners['track_group:%s' % t.id] = t.listeners.add(
'tracks', self.tracks_changed)
else:
connector = t.create_track_connector(message_cb=self.send_node_message)
yield from connector.init()

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import contextlib
import logging

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import base64
import email.parser
import email.policy

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
from fractions import Fraction
import getpass
import logging

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import fractions
import logging
import uuid

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import logging
import os

@ -52,7 +52,7 @@ class NodeDB(object):
self.db.cleanup()
def get_node_description(self, uri):
return self.db._nodes[uri]
return self.db._nodes[uri] # pylint: disable=protected-access
class BaseProjectTest(unittest.AsyncTestCase):
@ -191,7 +191,8 @@ class DeleteTrackTest(CommandTest):
self.project.add_track(
self.project.master_group, 0,
score_track.ScoreTrack(name='Test'))
self.project.master_group.tracks[0].instrument = 'sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2?bank=0&preset=0'
self.project.master_group.tracks[0].instrument = (
'sf2:/usr/share/sounds/sf2/FluidR3_GM.sf2?bank=0&preset=0')
cmd = project.RemoveTrack(
track_id=self.project.master_group.tracks[0].id)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
from noisicaa import core

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import asyncio
import errno
import functools

@ -25,7 +25,8 @@ import random
import struct
from noisidev import unittest
from . import render_settings_pb2
# TODO: pylint has issues with proto imports
from . import render_settings_pb2 # pylint: disable=no-name-in-module
from . import render

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
from fractions import Fraction
import logging
import random

@ -42,7 +42,7 @@ class NodeDB(object):
self.db.cleanup()
def get_node_description(self, uri):
return self.db._nodes[uri]
return self.db._nodes[uri] # pylint: disable=protected-access
class ControlTrackConnectorTest(unittest.AsyncTestCase):

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import functools
import logging

@ -39,7 +39,7 @@ class NodeDB(object):
self.db.cleanup()
def get_node_description(self, uri):
return self.db._nodes[uri]
return self.db._nodes[uri] # pylint: disable=protected-access

@ -260,4 +260,3 @@ class RootMixin(object):
refid = refid[1]
refobj = self.__obj_map[refid]
prop.__set__(node, refobj)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import json
from noisidev import unittest

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
from noisicaa import core

@ -42,7 +42,7 @@ class NodeDB(object):
self.db.cleanup()
def get_node_description(self, uri):
return self.db._nodes[uri]
return self.db._nodes[uri] # pylint: disable=protected-access
class Signal(object):
@ -101,4 +101,3 @@ class TrackGroupTest(unittest.AsyncTestCase):
duration_changed.clear()
del self.project.master_group.tracks[track.index]
self.assertTrue(duration_changed.is_set)

@ -20,6 +20,7 @@
#
# @end:license
class Mutation(object):
pass
@ -39,5 +40,3 @@ class RemoveNodeDescription(Mutation):
def __str__(self):
return '<RemoveNodeDescription uri="%s">' % self.uri

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import enum
import textwrap

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import io
import logging
from xml.etree import ElementTree

@ -19,4 +19,3 @@
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# @end:license

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
import os
import os.path
@ -152,4 +154,3 @@ class CSoundScanner(scanner.Scanner):
parameters=parameters)
yield uri, node_desc

@ -20,12 +20,16 @@
#
# @end:license
import logging
from noisidev import unittest
from . import csound_scanner
logger = logging.getLogger(__name__)
class CSoundScannerTest(unittest.TestCase):
def test_load_csound_nodes(self):
scanner = csound_scanner.CSoundScanner()
for uri, node_description in scanner.scan():
pass
for uri, _ in scanner.scan():
logging.info(uri)

@ -20,6 +20,8 @@
#
# @end:license
# TODO: pylint-unclean
import logging
import os
import os.path
@ -127,4 +129,3 @@ class LadspaScanner(scanner.Scanner):
parameters=parameters)
yield uri, node_desc

@ -20,12 +20,16 @@
#
# @end:license
import logging
from noisidev import unittest
from . import ladspa_scanner