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

BehaviorTree / BehaviorTree.CPP / 21643031337

03 Feb 2026 06:41PM UTC coverage: 77.317% (+7.7%) from 69.604%
21643031337

Pull #1102

github

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

14 of 15 new or added lines in 2 files covered. (93.33%)

1 existing line in 1 file now uncovered.

4196 of 5427 relevant lines covered (77.32%)

24141.66 hits per line

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

97.26
/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

23
  Duration first_timestamp = {};
24

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

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

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

36
// Base class constructor - initializes _p before StatusChangeLogger
37
detail::FileLogger2PImplBase::FileLogger2PImplBase()
5✔
38
  : _p(std::make_unique<FileLogger2Private>())
5✔
39
{
40
  // Acquire mutex once to establish happens-before relationship for TSAN.
41
  // This must happen before StatusChangeLogger's constructor subscribes to
42
  // callbacks that may access the mutex from other threads.
43
  std::scoped_lock lock(_p->queue_mutex);
5✔
44
}
5✔
45

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

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

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

62
  enableTransitionToIdle(true);
4✔
63

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

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

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

74
  const uint8_t protocol = 1;
4✔
75

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

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

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

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

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

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

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

97
  // Wait for writer thread to signal it's ready
98
  while(!_p->ready.load(std::memory_order_acquire))
278✔
99
  {
100
    std::this_thread::yield();
274✔
101
  }
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
  // Ignore callbacks that arrive before the writer thread is ready.
116
  // This can happen during StatusChangeLogger construction.
117
  if(!_p->ready.load(std::memory_order_acquire))
42✔
118
  {
NEW
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
  _p->file_stream.flush();
3✔
136
}
3✔
137

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

143
  // Signal that the writer thread is ready to accept callbacks.
144
  // IMPORTANT: This must happen AFTER acquiring the mutex for the first time,
145
  // to establish proper synchronization with the callback thread.
146
  {
147
    std::scoped_lock lock(_p->queue_mutex);
4✔
148
    _p->ready.store(true, std::memory_order_release);
4✔
149
  }
4✔
150

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

170
      _p->file_stream.write(write_buffer.data(), 9);
78✔
171
      transitions.pop_front();
39✔
172
    }
173
    _p->file_stream.flush();
19✔
174
  }
175
}
4✔
176

177
}  // 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