Replies: 9 comments 5 replies
-
I understand the convenience of being able to mock the low-level functions of the Arduino API. But EpoxyDuino was never intended to be a mocking library. It was supposed to emulate the Arduino API as closely as possible on a Unix/Posix system. The thinking was that if some code needs to be unit tested, then it should be designed such that it can be tested within the microcontroller itself, instead of having to rely on mocking functions that exist only in EpoxyDuino. I accepted the |
Beta Was this translation helpful? Give feedback.
-
Hi Brian I understand this point of view. I do not really consider time simulation as a mock, but exactly in the 'unit test' spirit that EpoxyDuino was designed for. But yes, this is no more under the microcontroller itself. Since I use EpoxyDuino, I find this library to be very powerfull and incredibly valuable for debugging some tricky code on the PC with a decent debugger. In fact, I even never unit testing on the microcontroller (maybe I should...). In my point of view or less: in my use of your libs
I've forked EpoxyDuino just to add the simulated time and be able to continue to use AUnit on github with the help of EpoxyDuino and EspMock. Of course it's up to you to decide, but honestly, I don't feel comfortable by forking just for that. (I"d prefer my fork to be temporary). I'd prefer to stick to your repo and delete mine. Of course, I do agree that simulated time won't run on a microcontroller and thus, breaks the rule that unit tests could be ran on the microcontroller also. I do not have a solution too. Best would be to have a overview on how EpoxyDuino is used. Maybe very few uses Epoxy to run unit tests on the microcontroller. My guess is that because EpoxyDuino make it possible to run tests on the PC, it is so more comfortable that noone would think to run it on the microcontroller. My typical use case : I've added many features to TinyMqtt + released without even testing them on the microcontroller. (which maybe not a so good habit :-D) That said, I can agree with you. The choice is hard to make and it's up to you. Maybe a solution would be replace functions by 'hooks'. EpoxyDuino sticks to the law : emulate api as close at the Arduino's one. Something like this (very bad code, just look at the idea behind): // EpoxyDuino changes:
long epoxy_millis() {...} // Default implementation
auto millis = epoxy_millis; // Definition to millis function millis is now a ptr on any function that can be overriden in ExtDuino // ExtDuino -> overrides some implementations
long simulated_millis(); // Able to work as millis or in simulated time
long init_extduino()
{
millis = simulated_mills;
} Not a good design (I'd prefer to group functions by theme such as Time, under a class such as EpoxyTime which would be polymorphic, and would allow to override functions. This way, we would keep EpoxyDuino in its orientation: provides an emulation of the API that is compatible with the microcontroller. And that allows to write unit tests designed for both uController and PC. And ExtDuino (or EpoxyTimeDuino or whatever) : provides a way to change some implementations. And thus breaks the rule of 'runs on microcontroller'. Another solution that may enforce to group functions together could be #include <memory>
long now();
namespace EpoxyDuino {
class TimeInterface
{
public:
virtual long millis() = 0;
virtual long micros() = 0;
virtual void delay(long) = 0;
virtual void delayMicroseconds(long) = 0;
};
class TimeImpl : public TimeInterface
{
public:
long millis() override { return now(); }
long micros() override { return now(); }
void delay(long) override {}
void delayMicroseconds(long) override {}
};
std::unique_ptr<EpoxyDuino::TimeInterface> time(new EpoxyDuino::TimeImpl);
}
long millis() { return EpoxyDuino::time->millis(); }
long micros() { return EpoxyDuino::time->micros(); }
void delay(long t) { EpoxyDuino::time->delay(t); }
void delayMicroseconds(long t) { EpoxyDuino::time->delay(t); }
// EXT_DUINO : Add simulated time feature inside EpoxyDuino, (breaking ability to run tests inside the uController)
namespace EpoxyDuinoSimulatedTime {
class TimeImpl : public EpoxyDuino::TimeInterface
{
public:
long millis() override { return time; }
long micros() override { return time; }
void delay(long) override {}
void delayMicroseconds(long) override {}
private:
long time;
};
void init_ext_duino()
{
EpoxyDuino::time.reset(new EpoxyDuinoSimulatedTime::TimeImpl);
}
} To be frankly, I like the first solution because it is very simple to implement at a low cost, and I like the second solution because functions are grouped by theme. But ... I don't really like for some unknown reasons boths. As you I have to think more about that too. |
Beta Was this translation helpful? Give feedback.
-
I've just thought about this, but millis() is intended to return the same value while it is called within interrupts. Only simulated time or thing near this could match this. Don't know what to do with this but I had to say. |
Beta Was this translation helpful? Give feedback.
-
Using ptr on functions is not such a important work, and little intrusive too. But I easylly can ear that you do not want to spend this time. But I'm ready to spend this time if you want. Writing unit tested libraries on Arduino and Esp seems to me to be the greatest value of EpoxyDuino, so I'll keep using my forked repo and will maintain it with your repo. Thanks a lot for this job. And for your time. |
Beta Was this translation helpful? Give feedback.
-
Yes I was thinking about extensions segregation into separate files. Not so easy. The first next thing I'll probably do is to distinguish between core functions and extra-core ones. I guess I'll put them on a dedicated namespace (CoreInjection, CoreTest, EpoxyTest ... whatever I' m not strong in naming...) I really want set_millis() not to be confused with millis() |
Beta Was this translation helpful? Give feedback.
-
Hello Brian I do not have any design now. My mind is to keep it as simple as possible. This may be a misunderstanding but as I'm not english, I may have used wrong words... In few words, if the compilation flag is not set, then my EpoxyDuino sould compile exactly the same as your, and will expose exactly the same API (the existing one). But if the flag is set, then some new functions appear, allowing to override some functions. In the case of my Simulated Time functions, you won't have access to them until the compilation flag is set. Just because the new functions does not belong to the Arduino API. They only appear once the flag is given. That said, those new functions should not belong to the global namespace for the same reason. Another way to explain what I mean is
Yet one can write with my current fork set_millis(100);
...
a = millis(); In this code, it is not clear that set_millis(100) does not belong to the standard but in this one: EpoxyTest::set_millis(100);
...
a = millis(); This is a lot more explicit. Now for TwoWire or other that are badly designed, it seems to me easy to bypass the original classes with But as I said, I will only extend what is needed for my needs (yet only set_time is usefull). group main
time 0 -> set_millis(0)
port A0 123
wait 10ms
port A0 127
wait 10ms
port A1 231
port A1 129
endgroup
group A2
port A2 100
sync 1s -> wait until time is one second (or more)
loop 100
port A2 100
loop 10
port A2++
wait 1ms
endloop
loop 5
port A2--
wait 1ms
endloop
endloop
endgroup
group A3
port A3 read port_a3.dat
engroup
group waves
port A4 square 0-250 10% 1kHz
port A5 sinus 10-80 100Hz
endgroup
group serial
wait_serial 0 0x02
serial 0 0xCAFE0102
echo "com started"
wait_serial 0 0x03
serial 0 0x04
echo "com end"
endgroup
...
|
Beta Was this translation helpful? Give feedback.
-
This commit hsaturn@33bde3e is a first attempt to do this. |
Beta Was this translation helpful? Give feedback.
-
This commit hsaturn@f221664 is a step toward the ability to write scenarios. |
Beta Was this translation helpful? Give feedback.
-
Script are available !!! Such a script makes pins of the Arduino change:
|
Beta Was this translation helpful? Give feedback.
-
Hello bxparks
My project (TinyMqtt) unit tests needs to simulate the time.
This helps to
The last point is important, because AUnit fails if unit tests take too long !
I've modified Arduino.* in order to achieve this.
All is working very well for TinyMqtt / EspMock with these modifications.
By the way, thank you for mentioning EspMock in your README, this mock now support network simulation !
Here are the modifications:
Beta Was this translation helpful? Give feedback.
All reactions