Lin_Simulator/cpp/tests/test_signal_editing.cpp
Mohamed Salem cb60c2ad5d Steps 2-7: LDF loading, signal editing, Rx display, connection, BabyLIN backend, scheduler
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>
2026-04-04 14:21:24 +02:00

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"