• 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

90.08
/src/loggers/bt_sqlite_logger.cpp
1
#include "behaviortree_cpp/loggers/bt_sqlite_logger.h"
2

3
#include "behaviortree_cpp/xml_parsing.h"
4

5
#include <iostream>
6
#include <sstream>
7
#include <stdexcept>
8

9
#include <sqlite3.h>
10

11
namespace BT
12
{
13

14
namespace
15
{
16
// Helper function to execute a SQL statement and check for errors
17
void execSQL(sqlite3* db, const std::string& sql)
89✔
18
{
19
  char* err_msg = nullptr;
89✔
20
  const int rc = sqlite3_exec(db, sql.c_str(), nullptr, nullptr, &err_msg);
89✔
21
  if(rc != SQLITE_OK)
89✔
22
  {
23
    std::string error = "SQL error: ";
×
24
    if(err_msg != nullptr)
×
25
    {
26
      error += err_msg;
×
27
      sqlite3_free(err_msg);
×
28
    }
29
    throw RuntimeError(error);
×
30
  }
×
31
}
89✔
32

33
// Helper function to prepare a statement
34
sqlite3_stmt* prepareStatement(sqlite3* db, const std::string& sql)
109✔
35
{
36
  sqlite3_stmt* stmt = nullptr;
109✔
37
  const int rc = sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr);
109✔
38
  if(rc != SQLITE_OK)
109✔
39
  {
40
    throw RuntimeError(std::string("Failed to prepare statement: ") + sqlite3_errmsg(db));
×
41
  }
42
  return stmt;
109✔
43
}
44

45
// Helper function to execute a prepared statement
46
void execStatement(sqlite3_stmt* stmt)
96✔
47
{
48
  const int rc = sqlite3_step(stmt);
96✔
49
  if(rc != SQLITE_DONE && rc != SQLITE_ROW)
96✔
50
  {
51
    throw RuntimeError(std::string("Failed to execute statement: ") + std::to_string(rc));
×
52
  }
53
  sqlite3_finalize(stmt);
96✔
54
}
96✔
55

56
}  // namespace
57

58
SqliteLogger::SqliteLogger(const Tree& tree, std::filesystem::path const& filepath,
14✔
59
                           bool append)
14✔
60
  : StatusChangeLogger(tree.rootNode())
14✔
61
{
62
  const auto extension = filepath.filename().extension();
14✔
63
  if(extension != ".db3" && extension != ".btdb")
14✔
64
  {
65
    throw RuntimeError("SqliteLogger: the file extension must be [.db3] or [.btdb]");
1✔
66
  }
67

68
  enableTransitionToIdle(true);
13✔
69

70
  // Open database
71
  const int rc = sqlite3_open(filepath.string().c_str(), &db_);
13✔
72
  if(rc != SQLITE_OK)
13✔
73
  {
74
    throw RuntimeError(std::string("Cannot open database: ") + sqlite3_errmsg(db_));
×
75
  }
76

77
  // Create tables
78
  execSQL(db_, "CREATE TABLE IF NOT EXISTS Transitions ("
26✔
79
               "timestamp  INTEGER PRIMARY KEY NOT NULL, "
80
               "session_id INTEGER NOT NULL, "
81
               "node_uid   INTEGER NOT NULL, "
82
               "duration   INTEGER, "
83
               "state      INTEGER NOT NULL,"
84
               "extra_data VARCHAR );");
85

86
  execSQL(db_, "CREATE TABLE IF NOT EXISTS Nodes ("
26✔
87
               "session_id INTEGER NOT NULL, "
88
               "fullpath   VARCHAR, "
89
               "node_uid   INTEGER NOT NULL );");
90

91
  execSQL(db_, "CREATE TABLE IF NOT EXISTS Definitions ("
13✔
92
               "session_id INTEGER PRIMARY KEY AUTOINCREMENT, "
93
               "date       TEXT NOT NULL,"
94
               "xml_tree   TEXT NOT NULL);");
95

96
  if(!append)
13✔
97
  {
98
    execSQL(db_, "DELETE from Transitions;");
24✔
99
    execSQL(db_, "DELETE from Definitions;");
24✔
100
    execSQL(db_, "DELETE from Nodes;");
24✔
101
  }
102

103
  // Insert tree definition
104
  auto tree_xml = WriteTreeToXML(tree, true, true);
13✔
105
  sqlite3_stmt* stmt = prepareStatement(db_, "INSERT into Definitions (date, xml_tree) "
13✔
106
                                             "VALUES (datetime('now','localtime'),?);");
107
  sqlite3_bind_text(stmt, 1, tree_xml.c_str(), -1, SQLITE_TRANSIENT);
13✔
108
  execStatement(stmt);
13✔
109

110
  // Get session_id
111
  stmt = prepareStatement(db_, "SELECT MAX(session_id) FROM Definitions LIMIT 1;");
13✔
112
  if(sqlite3_step(stmt) == SQLITE_ROW)
13✔
113
  {
114
    session_id_ = sqlite3_column_int(stmt, 0);
13✔
115
  }
116
  sqlite3_finalize(stmt);
13✔
117

118
  // Insert nodes
119
  for(const auto& subtree : tree.subtrees)
26✔
120
  {
121
    for(const auto& node : subtree->nodes)
50✔
122
    {
123
      stmt = prepareStatement(db_, "INSERT INTO Nodes VALUES (?, ?, ?)");
37✔
124
      sqlite3_bind_int(stmt, 1, session_id_);
37✔
125
      sqlite3_bind_text(stmt, 2, node->fullPath().c_str(), -1, SQLITE_TRANSIENT);
37✔
126
      sqlite3_bind_int(stmt, 3, node->UID());
37✔
127
      execStatement(stmt);
37✔
128
    }
129
  }
130

131
  writer_thread_ = std::thread(&SqliteLogger::writerLoop, this);
13✔
132
}
20✔
133

134
SqliteLogger::~SqliteLogger()
13✔
135
{
136
  try
137
  {
138
    loop_ = false;
13✔
139
    queue_cv_.notify_one();
13✔
140
    writer_thread_.join();
13✔
141
    flush();
13✔
142
    execSQL(db_, "PRAGMA optimize;");
26✔
143
  }
144
  catch(const std::exception& ex)
×
145
  {
146
    std::cerr << "Exception in ~SqliteLogger(): " << ex.what() << std::endl;
×
UNCOV
147
  }
×
148
  sqlite3_close(db_);
13✔
149
}
13✔
150

151
void SqliteLogger::setAdditionalCallback(ExtraCallback func)
1✔
152
{
153
  extra_func_ = func;
1✔
154
}
1✔
155

156
void SqliteLogger::callback(Duration timestamp, const TreeNode& node,
115✔
157
                            NodeStatus prev_status, NodeStatus status)
158
{
159
  using namespace std::chrono;
160
  const int64_t tm_usec = int64_t(duration_cast<microseconds>(timestamp).count());
115✔
161
  monotonic_timestamp_ = std::max(monotonic_timestamp_ + 1, tm_usec);
115✔
162

163
  long elapsed_time = 0;
115✔
164

165
  if(prev_status == NodeStatus::IDLE && status == NodeStatus::RUNNING)
115✔
166
  {
167
    starting_time_[&node] = monotonic_timestamp_;
17✔
168
  }
169

170
  if(prev_status == NodeStatus::RUNNING && status != NodeStatus::RUNNING)
115✔
171
  {
172
    elapsed_time = monotonic_timestamp_;
17✔
173
    auto it = starting_time_.find(&node);
17✔
174
    if(it != starting_time_.end())
17✔
175
    {
176
      elapsed_time -= it->second;
17✔
177
    }
178
  }
179

180
  Transition trans;
115✔
181
  trans.timestamp = monotonic_timestamp_;
115✔
182
  trans.duration = elapsed_time;
115✔
183
  trans.node_uid = node.UID();
115✔
184
  trans.status = status;
115✔
185

186
  if(extra_func_)
115✔
187
  {
188
    trans.extra_data = extra_func_(timestamp, node, prev_status, status);
7✔
189
  }
190

191
  {
192
    const std::scoped_lock lk(queue_mutex_);
115✔
193
    transitions_queue_.push_back(trans);
115✔
194
  }
115✔
195
  queue_cv_.notify_one();
115✔
196
}
115✔
197

198
void SqliteLogger::execSqlStatement(std::string statement)
1✔
199
{
200
  execSQL(db_, statement);
1✔
201
}
1✔
202

203
void SqliteLogger::writerLoop()
13✔
204
{
205
  std::deque<Transition> transitions;
13✔
206

207
  while(loop_)
36✔
208
  {
209
    transitions.clear();
23✔
210
    {
211
      std::unique_lock lk(queue_mutex_);
23✔
212
      queue_cv_.wait(lk, [this]() { return !transitions_queue_.empty() || !loop_; });
64✔
213
      std::swap(transitions, transitions_queue_);
23✔
214
    }
23✔
215

216
    while(!transitions.empty())
69✔
217
    {
218
      auto const trans = transitions.front();
46✔
219
      transitions.pop_front();
46✔
220

221
      sqlite3_stmt* stmt = prepareStatement(db_, "INSERT INTO Transitions VALUES (?, ?, "
46✔
222
                                                 "?, ?, ?, ?)");
223
      sqlite3_bind_int64(stmt, 1, trans.timestamp);
46✔
224
      sqlite3_bind_int(stmt, 2, session_id_);
46✔
225
      sqlite3_bind_int(stmt, 3, trans.node_uid);
46✔
226
      sqlite3_bind_int64(stmt, 4, trans.duration);
46✔
227
      sqlite3_bind_int(stmt, 5, static_cast<int>(trans.status));
46✔
228
      sqlite3_bind_text(stmt, 6, trans.extra_data.c_str(), -1, SQLITE_TRANSIENT);
46✔
229
      execStatement(stmt);
46✔
230
    }
46✔
231
  }
232
}
13✔
233

234
void BT::SqliteLogger::flush()
16✔
235
{
236
  sqlite3_db_cacheflush(db_);
16✔
237
}
16✔
238

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