kopia lustrzana https://github.com/NanoVNA-Saver/nanovna-saver
Increased test coverage
rodzic
e6d3ea0c12
commit
3bfd99ad3d
|
@ -10,7 +10,7 @@ omit =
|
||||||
NanoVNASaver/Inputs.py
|
NanoVNASaver/Inputs.py
|
||||||
NanoVNASaver/Marker/*.py
|
NanoVNASaver/Marker/*.py
|
||||||
NanoVNASaver/NanoVNASaver.py
|
NanoVNASaver/NanoVNASaver.py
|
||||||
NanoVNASaver/Settings.py
|
NanoVNASaver/Settings/Bands.py
|
||||||
NanoVNASaver/SweepWorker.py
|
NanoVNASaver/SweepWorker.py
|
||||||
NanoVNASaver/Windows/*.py
|
NanoVNASaver/Windows/*.py
|
||||||
**/__init__.py
|
**/__init__.py
|
||||||
|
|
|
@ -104,7 +104,7 @@ def format_group_delay(val: float) -> str:
|
||||||
|
|
||||||
|
|
||||||
def format_phase(val: float) -> str:
|
def format_phase(val: float) -> str:
|
||||||
return f"{math.degrees(val):.2f}\N{DEGREE SIGN}"
|
return f"{math.degrees(val):.2f}""\N{DEGREE SIGN}"
|
||||||
|
|
||||||
|
|
||||||
def format_complex_imp(z: complex, allow_negative: bool = False) -> str:
|
def format_complex_imp(z: complex, allow_negative: bool = False) -> str:
|
||||||
|
@ -113,7 +113,7 @@ def format_complex_imp(z: complex, allow_negative: bool = False) -> str:
|
||||||
fmt_re = FMT_COMPLEX_NEG
|
fmt_re = FMT_COMPLEX_NEG
|
||||||
re = SITools.Value(z.real, fmt=fmt_re)
|
re = SITools.Value(z.real, fmt=fmt_re)
|
||||||
im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX)
|
im = SITools.Value(abs(z.imag), fmt=FMT_COMPLEX)
|
||||||
return f"{re}{'-' if z.imag < 0 else '+'}j{im} \N{OHM SIGN}"
|
return f"{re}{'-' if z.imag < 0 else '+'}j{im} ""\N{OHM SIGN}"
|
||||||
|
|
||||||
def format_wavelength(length: Number) -> str:
|
def format_wavelength(length: Number) -> str:
|
||||||
return str(SITools.Value(length, "m", FMT_WAVELENGTH))
|
return str(SITools.Value(length, "m", FMT_WAVELENGTH))
|
||||||
|
|
|
@ -58,7 +58,7 @@ class Sweep():
|
||||||
def stepsize(self) -> int:
|
def stepsize(self) -> int:
|
||||||
return round(self.span / ((self.points -1) * self.segments))
|
return round(self.span / ((self.points -1) * self.segments))
|
||||||
|
|
||||||
def _exp_factor(self, index: int) -> int:
|
def _exp_factor(self, index: int) -> float:
|
||||||
return 1 - log(self.segments + 1 - index) / log(self.segments + 1)
|
return 1 - log(self.segments + 1 - index) / log(self.segments + 1)
|
||||||
|
|
||||||
def get_index_range(self, index: int) -> Tuple[int, int]:
|
def get_index_range(self, index: int) -> Tuple[int, int]:
|
||||||
|
@ -66,8 +66,8 @@ class Sweep():
|
||||||
start = self.start + index * self.points * self.step
|
start = self.start + index * self.points * self.step
|
||||||
end = start + (self.points - 1) * self.step
|
end = start + (self.points - 1) * self.step
|
||||||
else:
|
else:
|
||||||
start = self.start + self.span * self._exp_factor(index)
|
start = round(self.start + self.span * self._exp_factor(index))
|
||||||
end = self.start + self.span * self._exp_factor(index + 1)
|
end = round(self.start + self.span * self._exp_factor(index + 1))
|
||||||
logger.debug("get_index_range(%s) -> (%s, %s)", index, start, end)
|
logger.debug("get_index_range(%s) -> (%s, %s)", index, start, end)
|
||||||
return (start, end)
|
return (start, end)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# NanoVNASaver
|
# NanoVNASaver
|
||||||
#
|
|
||||||
# A python program to view and export Touchstone data from a NanoVNA
|
# A python program to view and export Touchstone data from a NanoVNA
|
||||||
# Copyright (C) 2019, 2020 Rune B. Broberg
|
# Copyright (C) 2019, 2020 Rune B. Broberg
|
||||||
# Copyright (C) 2020 NanoVNA-Saver Authors
|
# Copyright (C) 2020 NanoVNA-Saver Authors
|
||||||
|
@ -18,142 +17,10 @@
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import typing
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
|
||||||
from PyQt5.QtCore import QModelIndex
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BandsModel(QtCore.QAbstractTableModel):
|
|
||||||
bands: List[Tuple[str, int, int]] = []
|
|
||||||
enabled = False
|
|
||||||
color = QtGui.QColor(128, 128, 128, 48)
|
|
||||||
|
|
||||||
# These bands correspond broadly to the Danish Amateur Radio allocation
|
|
||||||
default_bands = ["2200 m;135700;137800",
|
|
||||||
"630 m;472000;479000",
|
|
||||||
"160 m;1800000;2000000",
|
|
||||||
"80 m;3500000;3800000",
|
|
||||||
"60 m;5250000;5450000",
|
|
||||||
"40 m;7000000;7200000",
|
|
||||||
"30 m;10100000;10150000",
|
|
||||||
"20 m;14000000;14350000",
|
|
||||||
"17 m;18068000;18168000",
|
|
||||||
"15 m;21000000;21450000",
|
|
||||||
"12 m;24890000;24990000",
|
|
||||||
"10 m;28000000;29700000",
|
|
||||||
"6 m;50000000;52000000",
|
|
||||||
"4 m;69887500;70512500",
|
|
||||||
"2 m;144000000;146000000",
|
|
||||||
"70 cm;432000000;438000000",
|
|
||||||
"23 cm;1240000000;1300000000",
|
|
||||||
"13 cm;2320000000;2450000000"]
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.settings = QtCore.QSettings(QtCore.QSettings.IniFormat,
|
|
||||||
QtCore.QSettings.UserScope,
|
|
||||||
"NanoVNASaver", "Bands")
|
|
||||||
self.settings.setIniCodec("UTF-8")
|
|
||||||
self.enabled = self.settings.value("ShowBands", False, bool)
|
|
||||||
|
|
||||||
stored_bands: List[str] = self.settings.value("bands", self.default_bands)
|
|
||||||
if stored_bands:
|
|
||||||
for b in stored_bands:
|
|
||||||
(name, start, end) = b.split(";")
|
|
||||||
self.bands.append((name, int(start), int(end)))
|
|
||||||
|
|
||||||
def saveSettings(self):
|
|
||||||
stored_bands = []
|
|
||||||
for b in self.bands:
|
|
||||||
stored_bands.append(b[0] + ";" + str(b[1]) + ";" + str(b[2]))
|
|
||||||
self.settings.setValue("bands", stored_bands)
|
|
||||||
self.settings.sync()
|
|
||||||
|
|
||||||
def resetBands(self):
|
|
||||||
self.bands = []
|
|
||||||
for b in self.default_bands:
|
|
||||||
(name, start, end) = b.split(";")
|
|
||||||
self.bands.append((name, int(start), int(end)))
|
|
||||||
self.layoutChanged.emit()
|
|
||||||
self.saveSettings()
|
|
||||||
|
|
||||||
def columnCount(self, parent: QModelIndex = ...) -> int:
|
|
||||||
return 3
|
|
||||||
|
|
||||||
def rowCount(self, parent: QModelIndex = ...) -> int:
|
|
||||||
return len(self.bands)
|
|
||||||
|
|
||||||
def data(self, index: QModelIndex, role: int = ...) -> QtCore.QVariant:
|
|
||||||
if (role == QtCore.Qt.DisplayRole or
|
|
||||||
role == QtCore.Qt.ItemDataRole or role == QtCore.Qt.EditRole):
|
|
||||||
return QtCore.QVariant(self.bands[index.row()][index.column()])
|
|
||||||
if role == QtCore.Qt.TextAlignmentRole:
|
|
||||||
if index.column() == 0:
|
|
||||||
return QtCore.QVariant(QtCore.Qt.AlignCenter)
|
|
||||||
return QtCore.QVariant(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
|
||||||
return QtCore.QVariant()
|
|
||||||
|
|
||||||
def setData(self, index: QModelIndex, value: typing.Any, role: int = ...) -> bool:
|
|
||||||
if role == QtCore.Qt.EditRole and index.isValid():
|
|
||||||
t = self.bands[index.row()]
|
|
||||||
name = t[0]
|
|
||||||
start = t[1]
|
|
||||||
end = t[2]
|
|
||||||
if index.column() == 0:
|
|
||||||
name = value
|
|
||||||
elif index.column() == 1:
|
|
||||||
start = value
|
|
||||||
elif index.column() == 2:
|
|
||||||
end = value
|
|
||||||
self.bands[index.row()] = (name, start, end)
|
|
||||||
self.dataChanged.emit(index, index)
|
|
||||||
self.saveSettings()
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def index(self, row: int, column: int, parent: QModelIndex = ...) -> QModelIndex:
|
|
||||||
return self.createIndex(row, column)
|
|
||||||
|
|
||||||
def addRow(self):
|
|
||||||
self.bands.append(("New", 0, 0))
|
|
||||||
self.dataChanged.emit(self.index(len(self.bands), 0), self.index(len(self.bands), 2))
|
|
||||||
self.layoutChanged.emit()
|
|
||||||
|
|
||||||
def removeRow(self, row: int, parent: QModelIndex = ...) -> bool:
|
|
||||||
self.bands.remove(self.bands[row])
|
|
||||||
self.layoutChanged.emit()
|
|
||||||
self.saveSettings()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def headerData(self, section: int,
|
|
||||||
orientation: QtCore.Qt.Orientation, role: int = ...):
|
|
||||||
if (role == QtCore.Qt.DisplayRole and
|
|
||||||
orientation == QtCore.Qt.Horizontal):
|
|
||||||
if section == 0:
|
|
||||||
return "Band"
|
|
||||||
if section == 1:
|
|
||||||
return "Start (Hz)"
|
|
||||||
if section == 2:
|
|
||||||
return "End (Hz)"
|
|
||||||
return "Invalid"
|
|
||||||
super().headerData(section, orientation, role)
|
|
||||||
|
|
||||||
def flags(self, index: QModelIndex) -> QtCore.Qt.ItemFlags:
|
|
||||||
if index.isValid():
|
|
||||||
return QtCore.Qt.ItemFlags(
|
|
||||||
QtCore.Qt.ItemIsEditable |
|
|
||||||
QtCore.Qt.ItemIsEnabled |
|
|
||||||
QtCore.Qt.ItemIsSelectable)
|
|
||||||
super().flags(index)
|
|
||||||
|
|
||||||
def setColor(self, color):
|
|
||||||
self.color = color
|
|
||||||
|
|
||||||
|
|
||||||
class Version:
|
class Version:
|
||||||
RXP = re.compile(r"""^
|
RXP = re.compile(r"""^
|
||||||
\D*
|
\D*
|
||||||
|
|
|
@ -77,7 +77,7 @@ class SweepSettingsWindow(QtWidgets.QWidget):
|
||||||
"averages. Common values are 3/0, 5/2, 9/4 and 25/6.\n")
|
"averages. Common values are 3/0, 5/2, 9/4 and 25/6.\n")
|
||||||
label.setWordWrap(True)
|
label.setWordWrap(True)
|
||||||
settings_layout.addRow(label)
|
settings_layout.addRow(label)
|
||||||
|
|
||||||
self.s21att = QtWidgets.QLineEdit("0")
|
self.s21att = QtWidgets.QLineEdit("0")
|
||||||
label = QtWidgets.QLabel(
|
label = QtWidgets.QLabel(
|
||||||
"Some times when you measure amplifiers you need to use an"
|
"Some times when you measure amplifiers you need to use an"
|
||||||
|
@ -88,7 +88,8 @@ class SweepSettingsWindow(QtWidgets.QWidget):
|
||||||
|
|
||||||
settings_layout.addRow("Attenuator in port CH1 (s21) in dB", self.s21att)
|
settings_layout.addRow("Attenuator in port CH1 (s21) in dB", self.s21att)
|
||||||
|
|
||||||
# settings_layout.addRow(QtWidgets.QLabel("Common values with un-un are 16.9 (49:1 2450) 9.54 (9:1 450)"))
|
# settings_layout.addRow(QtWidgets.QLabel(
|
||||||
|
# "Common values with un-un are 16.9 (49:1 2450) 9.54 (9:1 450)"))
|
||||||
|
|
||||||
self.continuous_sweep_radiobutton.toggled.connect(
|
self.continuous_sweep_radiobutton.toggled.connect(
|
||||||
lambda: self.app.worker.setContinuousSweep(
|
lambda: self.app.worker.setContinuousSweep(
|
||||||
|
|
|
@ -25,6 +25,15 @@ from NanoVNASaver import Formatting as fmt
|
||||||
class TestCases(unittest.TestCase):
|
class TestCases(unittest.TestCase):
|
||||||
|
|
||||||
def test_format_frequency(self):
|
def test_format_frequency(self):
|
||||||
|
self.assertEqual(fmt.format_frequency(1), '1.00000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency(12), '12.0000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency(123), '123.000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency(1234), '1.23400kHz')
|
||||||
|
self.assertEqual(fmt.format_frequency(1234567), '1.23457MHz')
|
||||||
|
self.assertEqual(fmt.format_frequency(1234567890), '1.23457GHz')
|
||||||
|
self.assertEqual(fmt.format_frequency(0), '0.00000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency(-1), '-1.00000Hz')
|
||||||
|
|
||||||
self.assertEqual(fmt.format_frequency_space(1), '1.00000 Hz')
|
self.assertEqual(fmt.format_frequency_space(1), '1.00000 Hz')
|
||||||
self.assertEqual(fmt.format_frequency_space(12), '12.0000 Hz')
|
self.assertEqual(fmt.format_frequency_space(12), '12.0000 Hz')
|
||||||
self.assertEqual(fmt.format_frequency_space(123), '123.000 Hz')
|
self.assertEqual(fmt.format_frequency_space(123), '123.000 Hz')
|
||||||
|
@ -34,6 +43,15 @@ class TestCases(unittest.TestCase):
|
||||||
self.assertEqual(fmt.format_frequency_space(0), '0.00000 Hz')
|
self.assertEqual(fmt.format_frequency_space(0), '0.00000 Hz')
|
||||||
self.assertEqual(fmt.format_frequency_space(-1), '-1.00000 Hz')
|
self.assertEqual(fmt.format_frequency_space(-1), '-1.00000 Hz')
|
||||||
|
|
||||||
|
self.assertEqual(fmt.format_frequency_short(1), '1.000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(12), '12.00Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(123), '123.0Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(1234), '1.234kHz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(1234567), '1.235MHz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(1234567890), '1.235GHz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(0), '0.000Hz')
|
||||||
|
self.assertEqual(fmt.format_frequency_short(-1), '-1.000Hz')
|
||||||
|
|
||||||
def test_format_frequency_inputs(self):
|
def test_format_frequency_inputs(self):
|
||||||
self.assertEqual(fmt.format_frequency_inputs(1), '1Hz')
|
self.assertEqual(fmt.format_frequency_inputs(1), '1Hz')
|
||||||
self.assertEqual(fmt.format_frequency_inputs(12), '12Hz')
|
self.assertEqual(fmt.format_frequency_inputs(12), '12Hz')
|
||||||
|
@ -65,6 +83,11 @@ class TestCases(unittest.TestCase):
|
||||||
self.assertEqual(fmt.format_vswr(1.234), '1.234')
|
self.assertEqual(fmt.format_vswr(1.234), '1.234')
|
||||||
self.assertEqual(fmt.format_vswr(12345.12345), '12345.123')
|
self.assertEqual(fmt.format_vswr(12345.12345), '12345.123')
|
||||||
|
|
||||||
|
def test_format_magnitude(self):
|
||||||
|
self.assertEqual(fmt.format_magnitude(1), '1.000')
|
||||||
|
self.assertEqual(fmt.format_magnitude(1.234), '1.234')
|
||||||
|
self.assertEqual(fmt.format_magnitude(12345.12345), '12345.123')
|
||||||
|
|
||||||
def test_format_resistance(self):
|
def test_format_resistance(self):
|
||||||
self.assertEqual(fmt.format_resistance(1), '1 \N{OHM SIGN}')
|
self.assertEqual(fmt.format_resistance(1), '1 \N{OHM SIGN}')
|
||||||
self.assertEqual(fmt.format_resistance(12), '12 \N{OHM SIGN}')
|
self.assertEqual(fmt.format_resistance(12), '12 \N{OHM SIGN}')
|
||||||
|
@ -112,3 +135,7 @@ class TestCases(unittest.TestCase):
|
||||||
self.assertEqual(fmt.format_complex_imp(complex(1234, -1234)), '1.23k-j1.23k \N{OHM SIGN}')
|
self.assertEqual(fmt.format_complex_imp(complex(1234, -1234)), '1.23k-j1.23k \N{OHM SIGN}')
|
||||||
self.assertEqual(fmt.format_complex_imp(complex(1.234, 1234)), '1.23+j1.23k \N{OHM SIGN}')
|
self.assertEqual(fmt.format_complex_imp(complex(1.234, 1234)), '1.23+j1.23k \N{OHM SIGN}')
|
||||||
self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3)), '- +j1.23m \N{OHM SIGN}')
|
self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3)), '- +j1.23m \N{OHM SIGN}')
|
||||||
|
self.assertEqual(fmt.format_complex_imp(complex(-1, 1.23e-3), True), '-1+j1.23m \N{OHM SIGN}')
|
||||||
|
|
||||||
|
def test_format_wavelength(self):
|
||||||
|
self.assertEqual(fmt.format_wavelength(12.3456), '12.35 m')
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# NanoVNASaver
|
||||||
|
#
|
||||||
|
# A python program to view and export Touchstone data from a NanoVNA
|
||||||
|
# Copyright (C) 2019, 2020 Rune B. Broberg
|
||||||
|
# Copyright (C) 2020 NanoVNA-Saver Authors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Import targets to be tested
|
||||||
|
from NanoVNASaver.Settings import Sweep
|
||||||
|
|
||||||
|
class TestCases(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_sweep(self):
|
||||||
|
sweep = Sweep()
|
||||||
|
self.assertEqual(str(sweep), 'Sweep(3600000, 30000000, 101, 1, False)')
|
||||||
|
self.assertTrue(Sweep(3600000) == sweep)
|
||||||
|
self.assertFalse(Sweep(3600001) == sweep)
|
||||||
|
self.assertRaises(ValueError, Sweep, -1)
|
||||||
|
sweep = Sweep(segments = 3)
|
||||||
|
self.assertEqual(sweep.get_index_range(1), (12488000, 21288000))
|
||||||
|
data = list(sweep.get_frequencies())
|
||||||
|
self.assertEqual(data[0], 3600000)
|
||||||
|
self.assertEqual(data[-1], 30000000)
|
||||||
|
sweep = Sweep(segments = 3, logarithmic=True)
|
||||||
|
self.assertEqual(sweep.get_index_range(1), (9078495, 16800000))
|
||||||
|
data = list(sweep.get_frequencies())
|
||||||
|
self.assertEqual(data[0], 3600000)
|
||||||
|
self.assertEqual(data[-1], 29869307)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# NanoVNASaver
|
||||||
|
#
|
||||||
|
# A python program to view and export Touchstone data from a NanoVNA
|
||||||
|
# Copyright (C) 2019, 2020 Rune B. Broberg
|
||||||
|
# Copyright (C) 2020 NanoVNA-Saver Authors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
# Import targets to be tested
|
||||||
|
from NanoVNASaver.Version import Version
|
||||||
|
|
||||||
|
class TestCases(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_version(self):
|
||||||
|
ver = Version("v1.2.3-test")
|
||||||
|
self.assertEqual(str(ver), '1.2.3-test')
|
||||||
|
self.assertLessEqual(ver, Version("1.2.4"))
|
||||||
|
self.assertFalse(ver > Version("1.2.4"))
|
||||||
|
self.assertFalse(ver > Version("1.2.3-u"))
|
||||||
|
self.assertTrue(Version("1.2.4") >= ver)
|
||||||
|
self.assertFalse(Version("0.0.0") == Version("0.0.0-rc"))
|
||||||
|
self.assertEqual(ver.major, 1)
|
||||||
|
self.assertEqual(ver.minor, 2)
|
||||||
|
self.assertEqual(ver.revision, 3)
|
||||||
|
self.assertEqual(ver.note, '-test')
|
||||||
|
Version("asdasd")
|
Ładowanie…
Reference in New Issue