• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wirenboard / wb-mqtt-gpio / 93

31 Jul 2026 10:43AM UTC coverage: 44.281% (+4.5%) from 39.772%
93

push

github

web-flow
Use exported vars instead of MAKEFLAGS for coverage options (#82)

493 of 1086 branches covered (45.4%)

875 of 1976 relevant lines covered (44.28%)

3.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.78
/test/gpiocounter.test.cpp
1
#include "config.h"
2
#include "declarations.h"
3
#include "gpio_chip_driver.h"
4
#include "gpio_counter.h"
5
#include "gpio_line.h"
6
#include "types.h"
7
#include <gtest/gtest.h>
8

9
class TFakeGpioChipDriver: public TGpioChipDriver
10
{
11
    using TGpioLines = std::vector<PGpioLine>;
12
    uint8_t LineReadVal;
13

14
public:
15
    TFakeGpioChipDriver(const PGpioLine& line, uint8_t fakeGpioLineVal): TGpioChipDriver()
5✔
16
    {
17
        LineReadVal = fakeGpioLineVal;
5✔
18
        AddFakeGpioLine(line);
5✔
19
        AutoDetectInterruptEdges();
5✔
20
    }
5✔
21

22
private:
23
    void AddFakeGpioLine(const PGpioLine& line)
5✔
24
    {
25
        Lines[line->GetFd()].push_back(line);
5✔
26
    }
5✔
27
    void ReadLinesValues(const TGpioLines&)
20✔
28
    {
29
        for (const auto& fdLines: Lines) {
40✔
30
            for (auto line: fdLines.second) {
40✔
31
                line->SetCachedValue(LineReadVal);
20✔
32
            }
20✔
33
        }
34
    }
20✔
35
    void ReListenLine(PGpioLine)
×
36
    {}
×
37
};
38

39
class TFakeGpioLine: public TGpioLine
40
{
41
public:
42
    TFakeGpioLine(const TGpioLineConfig& config): TGpioLine(config)
9✔
43
    {}
9✔
44
    bool IsHandled() const
57✔
45
    {
46
        return true;
57✔
47
    }
48
    bool IsOutput() const
57✔
49
    {
50
        return false;
57✔
51
    }
52
    std::string DescribeShort() const
12✔
53
    {
54
        return "Mocked gpio line";
24✔
55
    }
56
};
57

58
class TGpioCounterGetEdgeTest: public testing::Test
59
{
60
protected:
61
    TGpioLineConfig fakeGpioLineConfig;
62

63
    void SetUp()
6✔
64
    {
65
        fakeGpioLineConfig.DebounceTimeout = std::chrono::microseconds(0);
6✔
66
        fakeGpioLineConfig.Offset = 0;
6✔
67
        fakeGpioLineConfig.Name = "testline";
6✔
68
        fakeGpioLineConfig.Type = "water_meter";
6✔
69
    }
6✔
70
};
71

72
TEST_F(TGpioCounterGetEdgeTest, counter_edge_both)
8✔
73
{
74
    auto assumedEdge = EGpioEdge::BOTH;
2✔
75
    fakeGpioLineConfig.InterruptEdge = assumedEdge;
2✔
76
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
77
    auto fakeGpioChipDriver = std::make_shared<TFakeGpioChipDriver>(fakeGpioLine, 0);
2✔
78

79
    ASSERT_EQ(fakeGpioLine->GetInterruptEdge(), assumedEdge);
2✔
80
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetInterruptEdge(), assumedEdge);
2✔
81
}
2✔
82

83
TEST_F(TGpioCounterGetEdgeTest, counter_edge_rising)
8✔
84
{
85
    auto assumedEdge = EGpioEdge::RISING;
2✔
86
    fakeGpioLineConfig.InterruptEdge = assumedEdge;
2✔
87
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
88
    auto fakeGpioChipDriver = std::make_shared<TFakeGpioChipDriver>(fakeGpioLine, 0);
2✔
89

90
    ASSERT_EQ(fakeGpioLine->GetInterruptEdge(), assumedEdge);
2✔
91
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetInterruptEdge(), assumedEdge);
2✔
92
}
2✔
93

94
TEST_F(TGpioCounterGetEdgeTest, counter_edge_falling)
8✔
95
{
96
    auto assumedEdge = EGpioEdge::FALLING;
2✔
97
    fakeGpioLineConfig.InterruptEdge = assumedEdge;
2✔
98
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
99
    auto fakeGpioChipDriver = std::make_shared<TFakeGpioChipDriver>(fakeGpioLine, 0);
2✔
100

101
    ASSERT_EQ(fakeGpioLine->GetInterruptEdge(), assumedEdge);
2✔
102
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetInterruptEdge(), assumedEdge);
2✔
103
}
2✔
104

105
TEST_F(TGpioCounterGetEdgeTest, counter_edge_auto_falling)
8✔
106
{
107
    fakeGpioLineConfig.InterruptEdge = EGpioEdge::AUTO;
2✔
108
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
109
    const auto fakeGpioChipDriver = std::make_shared<TFakeGpioChipDriver>(fakeGpioLine, 1);
2✔
110

111
    ASSERT_EQ(fakeGpioLine->GetInterruptEdge(), EGpioEdge::FALLING);
2✔
112
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetInterruptEdge(), EGpioEdge::FALLING);
2✔
113
}
2✔
114

115
TEST_F(TGpioCounterGetEdgeTest, counter_edge_auto_rising)
8✔
116
{
117
    fakeGpioLineConfig.InterruptEdge = EGpioEdge::AUTO;
2✔
118
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
119
    const auto fakeGpioChipDriver = std::make_shared<TFakeGpioChipDriver>(fakeGpioLine, 0);
2✔
120

121
    ASSERT_EQ(fakeGpioLine->GetInterruptEdge(), EGpioEdge::RISING);
2✔
122
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetInterruptEdge(), EGpioEdge::RISING);
2✔
123
}
2✔
124

125
TEST_F(TGpioCounterGetEdgeTest, counter_update_current)
8✔
126
{
127
    fakeGpioLineConfig.InterruptEdge = EGpioEdge::RISING;
2✔
128
    const auto fakeGpioLine = std::make_shared<TFakeGpioLine>(fakeGpioLineConfig);
2✔
129
    auto interval = std::chrono::microseconds(100000);
2✔
130
    auto assumedCurrent = (3600.0 * 1000000 * 1.0 / (interval.count() * fakeGpioLineConfig.Multiplier));
2✔
131

132
    fakeGpioLine->GetCounter()->HandleInterrupt(EGpioEdge::RISING, interval);
2✔
133
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetTotal(), 1);
2✔
134
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetCurrent(), assumedCurrent);
2✔
135

136
    fakeGpioLine->GetCounter()->Update(std::chrono::microseconds(200000));
2✔
137
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetCurrent(), assumedCurrent / 2);
2✔
138

139
    fakeGpioLine->GetCounter()->Update(std::chrono::microseconds(200000));
2✔
140
    ASSERT_EQ(fakeGpioLine->GetCounter()->GetCurrent(), assumedCurrent / 4);
2✔
141
}
2✔
142

143
// Regression harness for the "all inputs disappearing" bug. A counter line on a
144
// chip without interrupt support shares a single polled fd with all the other
145
// inputs. AutoDetectInterruptEdges() must NOT call ReListenLine() for such a
146
// line, otherwise the shared fd is erased (and closed), killing every input
147
// behind it.
148
class TFakePolledChipDriver: public TGpioChipDriver
149
{
150
    using TGpioLines = std::vector<PGpioLine>;
151
    uint8_t LineReadVal;
152

153
public:
154
    int ReListenCalls = 0;
155

156
    explicit TFakePolledChipDriver(uint8_t fakeGpioLineVal): TGpioChipDriver()
2✔
157
    {
158
        LineReadVal = fakeGpioLineVal;
2✔
159
    }
2✔
160

161
    // Put the line onto a shared polling fd, mimicking InitLinesPolling().
162
    // NB: we set only the map key, NOT line->SetFd(): SetFd() calls UpdateInfo()
163
    // which ioctl()s AccessChip(), and these fake lines have no chip — in a
164
    // release (NDEBUG) build the assert in AccessChip() is a no-op, so it would
165
    // dereference a null shared_ptr and segfault. TFakeGpioLine::IsHandled()
166
    // already returns true unconditionally, so the fd on the line is not needed.
167
    void AddPolledLine(const PGpioLine& line, int sharedFd)
3✔
168
    {
169
        Lines[sharedFd].push_back(line);
3✔
170
    }
3✔
171

172
    void Detect()
2✔
173
    {
174
        AutoDetectInterruptEdges();
2✔
175
    }
2✔
176

177
    size_t LinesOnFd(int fd) const
1✔
178
    {
179
        const auto it = Lines.find(fd);
1✔
180
        return it == Lines.end() ? 0 : it->second.size();
1✔
181
    }
182

183
private:
184
    void ReadLinesValues(const TGpioLines&) override
20✔
185
    {
186
        for (const auto& fdLines: Lines) {
40✔
187
            for (const auto& line: fdLines.second) {
50✔
188
                line->SetCachedValue(LineReadVal);
30✔
189
            }
190
        }
191
    }
20✔
192
    void ReListenLine(PGpioLine) override
1✔
193
    {
194
        ++ReListenCalls;
1✔
195
    }
1✔
196
};
197

198
class TGpioCounterPollingTest: public testing::Test
199
{
200
protected:
201
    TGpioLineConfig MakeLineConfig(uint32_t offset, const std::string& type)
3✔
202
    {
203
        TGpioLineConfig config;
3✔
204
        config.DebounceTimeout = std::chrono::microseconds(0);
3✔
205
        config.Offset = offset;
3✔
206
        config.Name = "line" + std::to_string(offset);
3✔
207
        config.Type = type;
3✔
208
        config.Direction = EGpioDirection::Input;
3✔
209
        config.InterruptEdge = EGpioEdge::AUTO;
3✔
210
        return config;
3✔
211
    }
×
212
};
213

214
// A polled counter line (no interrupt support) sharing an fd with a plain input
215
// must not trigger ReListenLine, and the shared fd must keep all its lines.
216
TEST_F(TGpioCounterPollingTest, polled_counter_keeps_shared_fd)
8✔
217
{
218
    // A fake, high-numbered fd, chosen to make a collision with a real open fd
219
    // in this test process unlikely. The base dtor close()s every fd key in
220
    // Lines; close() on a fd this process never opened just fails harmlessly.
221
    const int sharedFd = 100042;
2✔
222

223
    auto counterLine = std::make_shared<TFakeGpioLine>(MakeLineConfig(0, "water_meter"));
4✔
224
    counterLine->SetInterruptSupport(EInterruptSupport::NO);
2✔
225

226
    auto plainInput = std::make_shared<TFakeGpioLine>(MakeLineConfig(1, ""));
4✔
227
    plainInput->SetInterruptSupport(EInterruptSupport::NO);
2✔
228

229
    auto driver = std::make_shared<TFakePolledChipDriver>(1);
2✔
230
    driver->AddPolledLine(counterLine, sharedFd);
2✔
231
    driver->AddPolledLine(plainInput, sharedFd);
2✔
232

233
    driver->Detect();
2✔
234

235
    // Edge must still be auto-detected via polling (read value 1 => falling).
236
    ASSERT_EQ(counterLine->GetCounter()->GetInterruptEdge(), EGpioEdge::FALLING);
2✔
237
    // But no re-listen must happen for a polled line ...
238
    ASSERT_EQ(driver->ReListenCalls, 0);
2✔
239
    // ... and the shared fd must keep both lines.
240
    ASSERT_EQ(driver->LinesOnFd(sharedFd), 2u);
2✔
241
}
2✔
242

243
// A counter line that does support interrupts must still be re-listened so the
244
// freshly detected edge takes effect.
245
TEST_F(TGpioCounterPollingTest, interrupt_counter_is_relistened)
8✔
246
{
247
    const int ownFd = 100007;
2✔
248

249
    auto counterLine = std::make_shared<TFakeGpioLine>(MakeLineConfig(0, "water_meter"));
4✔
250
    counterLine->SetInterruptSupport(EInterruptSupport::YES);
2✔
251

252
    auto driver = std::make_shared<TFakePolledChipDriver>(0);
2✔
253
    driver->AddPolledLine(counterLine, ownFd);
2✔
254

255
    driver->Detect();
2✔
256

257
    // Read value 0 => rising.
258
    ASSERT_EQ(counterLine->GetCounter()->GetInterruptEdge(), EGpioEdge::RISING);
2✔
259
    ASSERT_EQ(driver->ReListenCalls, 1);
2✔
260
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc