Step 2 - LDF Loading: - ldfparser integration (Python) / custom regex parser (C++) - QTreeWidget with expandable signal rows, merged Value column - Hex/Dec toggle, FreeFormat schedule entries, auto-reload - Baud rate auto-detection from LDF Step 3 - Signal Editing: - Bit packing/unpacking (signal value ↔ frame bytes) - ReadOnlyColumnDelegate for per-column editability - Value clamping to signal width, recursion guard Step 4 - Rx Panel: - receive_rx_frame() API with timestamp, signal unpacking - Change highlighting (yellow), auto-scroll toggle, clear button - Dashboard view (in-place update per frame_id) Step 5 - Connection Panel: - ConnectionManager with state machine (Disconnected/Connecting/Connected/Error) - Port scanning (pyserial / QSerialPort), connect/disconnect with UI mapping Step 6 - BabyLIN Backend: - BabyLinBackend wrapping Lipowsky BabyLIN_library.py DLL - Mock mode for macOS/CI, device scan, SDF loading, signal access - Frame callbacks, raw command access Step 7 - Master Scheduler: - QTimer-based schedule execution with start/stop/pause - Frame sent callback with visual highlighting - Mock Rx simulation, manual send, global rate override Tests: Python 171 | C++ 124 (Steps 1-5 parity, Steps 6-7 Python-first) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
159 lines
4.6 KiB
C++
159 lines
4.6 KiB
C++
/**
|
|
* test_signal_editing.cpp — Tests for Step 3: signal ↔ frame byte sync.
|
|
* Tests bit packing/unpacking and signal value editing.
|
|
*/
|
|
|
|
#include <QtTest/QtTest>
|
|
#include <QTreeWidget>
|
|
#include <QTreeWidgetItem>
|
|
#include "main_window.h"
|
|
|
|
#ifndef LDF_SAMPLE_PATH
|
|
#error "LDF_SAMPLE_PATH must be defined by CMake"
|
|
#endif
|
|
|
|
class TestSignalEditing : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private:
|
|
MainWindow* m_window;
|
|
|
|
private slots:
|
|
void init()
|
|
{
|
|
m_window = new MainWindow();
|
|
m_window->loadLdfFile(QString(LDF_SAMPLE_PATH));
|
|
}
|
|
void cleanup() { delete m_window; m_window = nullptr; }
|
|
|
|
// ─── Bit Packing Unit Tests ───────────────────────────────────
|
|
|
|
void test_packSingleBit()
|
|
{
|
|
QVector<int> buf = {0, 0};
|
|
MainWindow::packSignal(buf, 0, 1, 1);
|
|
QCOMPARE(buf[0], 0x01);
|
|
}
|
|
|
|
void test_packByteAtOffset8()
|
|
{
|
|
QVector<int> buf = {0, 0};
|
|
MainWindow::packSignal(buf, 8, 8, 0x80);
|
|
QCOMPARE(buf[1], 0x80);
|
|
}
|
|
|
|
void test_pack2bitAtOffset1()
|
|
{
|
|
QVector<int> buf = {0, 0};
|
|
MainWindow::packSignal(buf, 1, 2, 3);
|
|
QCOMPARE(buf[0], 0x06);
|
|
}
|
|
|
|
void test_packMultipleSignals()
|
|
{
|
|
QVector<int> buf = {0, 0};
|
|
MainWindow::packSignal(buf, 0, 1, 1); // MotorEnable
|
|
MainWindow::packSignal(buf, 1, 2, 2); // MotorDirection
|
|
MainWindow::packSignal(buf, 8, 8, 128); // MotorSpeed
|
|
QCOMPARE(buf[0], 0x05);
|
|
QCOMPARE(buf[1], 0x80);
|
|
}
|
|
|
|
void test_extractSingleBit()
|
|
{
|
|
QVector<int> buf = {0x01, 0};
|
|
QCOMPARE(MainWindow::extractSignal(buf, 0, 1), 1);
|
|
}
|
|
|
|
void test_extractByteAtOffset8()
|
|
{
|
|
QVector<int> buf = {0, 0x80};
|
|
QCOMPARE(MainWindow::extractSignal(buf, 8, 8), 0x80);
|
|
}
|
|
|
|
void test_extract2bitAtOffset1()
|
|
{
|
|
QVector<int> buf = {0x06, 0};
|
|
QCOMPARE(MainWindow::extractSignal(buf, 1, 2), 3);
|
|
}
|
|
|
|
void test_packThenExtractRoundtrip()
|
|
{
|
|
QVector<int> buf = {0, 0};
|
|
MainWindow::packSignal(buf, 8, 8, 200);
|
|
QCOMPARE(MainWindow::extractSignal(buf, 8, 8), 200);
|
|
}
|
|
|
|
// ─── Signal Value Editing ─────────────────────────────────────
|
|
|
|
void test_editSignalUpdatesFrameBytes()
|
|
{
|
|
auto* frameItem = m_window->txTable()->topLevelItem(0);
|
|
auto* speedSig = frameItem->child(2); // MotorSpeed (bit 8, width 8)
|
|
|
|
// Simulate user edit
|
|
speedSig->setText(4, "128");
|
|
|
|
// Frame bytes should reflect MotorSpeed=128
|
|
QVariantList bytes = frameItem->data(4, Qt::UserRole).toList();
|
|
QCOMPARE(bytes[1].toInt(), 128);
|
|
}
|
|
|
|
void test_editSignalPreservesOtherSignals()
|
|
{
|
|
auto* frameItem = m_window->txTable()->topLevelItem(0);
|
|
auto* enableSig = frameItem->child(0);
|
|
auto* speedSig = frameItem->child(2);
|
|
|
|
enableSig->setText(4, "1");
|
|
speedSig->setText(4, "255");
|
|
|
|
QVariantList bytes = frameItem->data(4, Qt::UserRole).toList();
|
|
QVERIFY(bytes[0].toInt() & 0x01); // MotorEnable still 1
|
|
QCOMPARE(bytes[1].toInt(), 255);
|
|
}
|
|
|
|
void test_signalValueClampedToWidth()
|
|
{
|
|
auto* frameItem = m_window->txTable()->topLevelItem(0);
|
|
auto* enableSig = frameItem->child(0); // width 1, max = 1
|
|
|
|
enableSig->setText(4, "5");
|
|
|
|
int stored = enableSig->data(4, Qt::UserRole).toInt();
|
|
QVERIFY(stored <= 1);
|
|
}
|
|
|
|
// ─── Frame Value Editing ──────────────────────────────────────
|
|
|
|
void test_editFrameBytesUpdatesSignals()
|
|
{
|
|
auto* frameItem = m_window->txTable()->topLevelItem(0);
|
|
|
|
frameItem->setText(4, "05 C8");
|
|
|
|
// MotorEnable (bit 0, w1) = 1
|
|
QCOMPARE(frameItem->child(0)->data(4, Qt::UserRole).toInt(), 1);
|
|
// MotorDirection (bit 1, w2) = 2
|
|
QCOMPARE(frameItem->child(1)->data(4, Qt::UserRole).toInt(), 2);
|
|
// MotorSpeed (bit 8, w8) = 200
|
|
QCOMPARE(frameItem->child(2)->data(4, Qt::UserRole).toInt(), 200);
|
|
}
|
|
|
|
void test_editFrameBytesInvalidReverts()
|
|
{
|
|
auto* frameItem = m_window->txTable()->topLevelItem(0);
|
|
|
|
QVariantList oldBytes = frameItem->data(4, Qt::UserRole).toList();
|
|
|
|
frameItem->setText(4, "not valid hex");
|
|
|
|
QVariantList newBytes = frameItem->data(4, Qt::UserRole).toList();
|
|
QCOMPARE(newBytes, oldBytes);
|
|
}
|
|
};
|
|
|
|
QTEST_MAIN(TestSignalEditing)
|
|
#include "test_signal_editing.moc"
|