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

BehaviorTree / BehaviorTree.CPP / 21647612930

03 Feb 2026 08:59PM UTC coverage: 77.338% (+7.7%) from 69.604%
21647612930

Pull #1102

github

web-flow
Merge 5057dd7b9 into 2626fc59f
Pull Request #1102: Improve test suite quality and coverage

33 of 34 new or added lines in 4 files covered. (97.06%)

4 existing lines in 3 files now uncovered.

4201 of 5432 relevant lines covered (77.34%)

23919.65 hits per line

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

97.44
/src/loggers/bt_file_logger_v2.cpp
1
#include "behaviortree_cpp/loggers/bt_file_logger_v2.h"
2

3
#include "behaviortree_cpp/xml_parsing.h"
4

5
#include "flatbuffers/base.h"
6

7
namespace BT
8
{
9

10
namespace
11
{
12
int64_t ToUsec(Duration ts)
46✔
13
{
14
  return std::chrono::duration_cast<std::chrono::microseconds>(ts).count();
46✔
15
}
16
}  // namespace
17

18
// Define the private implementation struct
19
struct detail::FileLogger2Private
20
{
21
  std::ofstream file_stream;
22
  std::mutex file_mutex;  // Protects file_stream access from multiple threads
23

24
  Duration first_timestamp = {};
25

26
  std::deque<FileLogger2::Transition> transitions_queue;
27
  std::condition_variable queue_cv;
28
  std::mutex queue_mutex;
29

30
  std::thread writer_thread;
31
  std::atomic_bool loop = true;
32

33
  // Atomic flag for thread startup - callbacks check this before accessing queue
34
  std::atomic_bool ready = false;
35
};
36

37
// Base class constructor - initializes _p before StatusChangeLogger
38
detail::FileLogger2PImplBase::FileLogger2PImplBase()
5✔
39
  : _p(std::make_unique<FileLogger2Private>())
5✔
40
{
41
  // _p is now ready for use. StatusChangeLogger (constructed next) will
42
  // subscribe to callbacks that may immediately start queuing transitions.
43
}
5✔
44

45
// Destructor must be defined where FileLogger2Private is complete
46
detail::FileLogger2PImplBase::~FileLogger2PImplBase() = default;
5✔
47

48
FileLogger2::FileLogger2(const BT::Tree& tree, std::filesystem::path const& filepath)
5✔
49
  : StatusChangeLogger(tree.rootNode())
5✔
50
{
51
  // Note: _p is already initialized by FileLogger2PImplBase before
52
  // StatusChangeLogger's constructor runs. This is critical because
53
  // StatusChangeLogger subscribes to callbacks in its constructor,
54
  // and those callbacks may access _p.
55

56
  if(filepath.filename().extension() != ".btlog")
5✔
57
  {
58
    throw RuntimeError("FileLogger2: the file extension must be [.btlog]");
1✔
59
  }
60

61
  enableTransitionToIdle(true);
4✔
62

63
  //-------------------------------------
64
  _p->file_stream.open(filepath, std::ofstream::binary | std::ofstream::out);
4✔
65

66
  if(!_p->file_stream.is_open())
4✔
67
  {
UNCOV
68
    throw RuntimeError("problem opening file in FileLogger2");
×
69
  }
70

71
  _p->file_stream << "BTCPP4-FileLogger2";
4✔
72

73
  const uint8_t protocol = 1;
4✔
74

75
  _p->file_stream << protocol;
4✔
76

77
  std::string const xml = WriteTreeToXML(tree, true, true);
4✔
78

79
  // serialize the length of the buffer in the first 4 bytes
80
  std::array<char, 8> write_buffer{};
4✔
81
  flatbuffers::WriteScalar(write_buffer.data(), static_cast<int32_t>(xml.size()));
8✔
82
  _p->file_stream.write(write_buffer.data(), 4);
8✔
83

84
  // write the XML definition
85
  _p->file_stream.write(xml.data(), static_cast<std::streamsize>(xml.size()));
4✔
86

87
  _p->first_timestamp = std::chrono::system_clock::now().time_since_epoch();
4✔
88

89
  // save the first timestamp in the next 8 bytes (microseconds)
90
  const int64_t timestamp_usec = ToUsec(_p->first_timestamp);
4✔
91
  flatbuffers::WriteScalar(write_buffer.data(), timestamp_usec);
4✔
92
  _p->file_stream.write(write_buffer.data(), 8);
8✔
93

94
  _p->writer_thread = std::thread(&FileLogger2::writerLoop, this);
4✔
95

96
  // Wait for writer thread to signal it's ready using condition variable
97
  {
98
    std::unique_lock lock(_p->queue_mutex);
4✔
99
    _p->queue_cv.wait(lock,
4✔
100
                      [this]() { return _p->ready.load(std::memory_order_relaxed); });
8✔
101
  }
4✔
102
}
6✔
103

104
FileLogger2::~FileLogger2()
4✔
105
{
106
  _p->loop = false;
4✔
107
  _p->queue_cv.notify_one();
4✔
108
  _p->writer_thread.join();
4✔
109
  _p->file_stream.close();
4✔
110
}
4✔
111

112
void FileLogger2::callback(Duration timestamp, const TreeNode& node,
42✔
113
                           NodeStatus /*prev_status*/, NodeStatus status)
114
{
115
  // Don't queue callbacks until writer thread is ready - the constructor
116
  // holds queue_mutex while waiting, so we'd deadlock.
117
  if(!_p->ready.load(std::memory_order_acquire))
42✔
118
  {
NEW
UNCOV
119
    return;
×
120
  }
121

122
  Transition trans{};
42✔
123
  trans.timestamp_usec = uint64_t(ToUsec(timestamp - _p->first_timestamp));
42✔
124
  trans.node_uid = node.UID();
42✔
125
  trans.status = static_cast<uint64_t>(status);
42✔
126
  {
127
    const std::scoped_lock lock(_p->queue_mutex);
42✔
128
    _p->transitions_queue.push_back(trans);
42✔
129
  }
42✔
130
  _p->queue_cv.notify_one();
42✔
131
}
132

133
void FileLogger2::flush()
3✔
134
{
135
  const std::scoped_lock lock(_p->file_mutex);
3✔
136
  _p->file_stream.flush();
3✔
137
}
3✔
138

139
void FileLogger2::writerLoop()
4✔
140
{
141
  // local buffer in this thread
142
  std::deque<Transition> transitions;
4✔
143

144
  // Signal that the writer thread is ready.
145
  // The mutex + notify establishes happens-before with the constructor's wait.
146
  {
147
    std::scoped_lock lock(_p->queue_mutex);
4✔
148
    _p->ready.store(true, std::memory_order_relaxed);
4✔
149
  }
4✔
150
  _p->queue_cv.notify_all();
4✔
151

152
  while(_p->loop)
26✔
153
  {
154
    transitions.clear();
22✔
155
    {
156
      std::unique_lock lock(_p->queue_mutex);
22✔
157
      _p->queue_cv.wait_for(lock, std::chrono::milliseconds(10), [this]() {
22✔
158
        return !_p->transitions_queue.empty() && _p->loop;
39✔
159
      });
160
      // simple way to pop all the transitions from _p->transitions_queue into transitions
161
      std::swap(transitions, _p->transitions_queue);
22✔
162
    }
22✔
163
    {
164
      const std::scoped_lock file_lock(_p->file_mutex);
22✔
165
      while(!transitions.empty())
58✔
166
      {
167
        const auto trans = transitions.front();
36✔
168
        std::array<char, 9> write_buffer{};
36✔
169
        std::memcpy(write_buffer.data(), &trans.timestamp_usec, 6);
36✔
170
        std::memcpy(write_buffer.data() + 6, &trans.node_uid, 2);
36✔
171
        std::memcpy(write_buffer.data() + 8, &trans.status, 1);
36✔
172

173
        _p->file_stream.write(write_buffer.data(), 9);
72✔
174
        transitions.pop_front();
36✔
175
      }
176
      _p->file_stream.flush();
22✔
177
    }
22✔
178
  }
179
}
4✔
180

181
}  // namespace BT
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc