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

ossia / score / 33560887155

01 Sep 2026 09:24PM UTC coverage: 24.648% (+0.7%) from 23.997%
33560887155

Pull #2179

github

web-flow
Merge ecc0a2ad3 into 50d28933c
Pull Request #2179: Survive a build that does not have every plug-in

408 of 757 new or added lines in 47 files covered. (53.9%)

15 existing lines in 12 files now uncovered.

55851 of 226593 relevant lines covered (24.65%)

61526.39 hits per line

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

92.94
/src/plugins/score-lib-device/Device/Protocol/DeviceSettingsSerialization.cpp
1
// This is an open source non-commercial project. Dear PVS-Studio, please check
2
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
#include "DeviceSettings.hpp"
4
#include "ProtocolFactoryInterface.hpp"
5

6
#include <Device/Protocol/ProtocolList.hpp>
7

8
#include <score/application/ApplicationContext.hpp>
9
#include <score/plugins/InterfaceList.hpp>
10
#include <score/plugins/StringFactoryKey.hpp>
11
#include <score/plugins/StringFactoryKeySerialization.hpp>
12
#include <score/serialization/DataStreamVisitor.hpp>
13
#include <score/serialization/JSONValueVisitor.hpp>
14
#include <score/serialization/JSONVisitor.hpp>
15

16
#include <QDebug>
17

18
#include <stdexcept>
19

UNCOV
20
SCORE_SERALIZE_DATASTREAM_DEFINE(Device::DeviceSettings)
×
21

22
namespace
23
{
24
[[noreturn]] void throwMissingProtocol(const Device::DeviceSettings& n)
2✔
25
{
26
  // The binary format writes protocol settings inline with no length prefix, so
27
  // a reader without the factory cannot skip them: it lands mid-payload on the
28
  // trailing delimiter and reports the whole file as corrupt (and SIGTRAPs on
29
  // the way, since checkDelimiter breakpoints before it throws). Say what is
30
  // actually wrong instead.
31
  const QString msg
32
      = QStringLiteral(
6✔
33
            "device '%1' uses protocol %2, which this build does not have. The "
34
            "binary format cannot preserve settings for an unknown protocol: use "
35
            "the JSON .score format to move this document between machines.")
36
            .arg(n.name)
2✔
37
            .arg(QString::fromUtf8(score::uuids::toByteArray(n.protocol.impl())));
2✔
38
  throw std::runtime_error{msg.toStdString()};
2✔
39
}
4✔
40

41
bool isReservedMember(const rapidjson::Value::Member& m) noexcept
22✔
42
{
43
  const auto& s = score::StringConstant();
22✔
44
  const std::string_view name{m.name.GetString(), m.name.GetStringLength()};
22✔
45
  return name == s.Name || name == s.Protocol;
22✔
46
}
47

48
//! Everything in the serialized device except the two members score itself
49
//! owns, i.e. exactly what the protocol factory would have written.
50
QByteArray captureProtocolMembers(const rapidjson::Value& base)
5✔
51
{
52
  if(!base.IsObject())
5✔
NEW
53
    return {};
×
54

55
  rapidjson::StringBuffer buf;
5✔
56
  JsonWriter w{buf};
5✔
57
  w.StartObject();
5✔
58
  for(const auto& m : base.GetObject())
27✔
59
  {
60
    if(isReservedMember(m))
22✔
61
      continue;
10✔
62
    w.Key(m.name.GetString(), m.name.GetStringLength());
12✔
63
    m.value.Accept(w);
12✔
64
  }
65
  w.EndObject();
5✔
66

67
  // An object with no protocol-specific members is not worth carrying around.
68
  if(buf.GetLength() <= 2)
5✔
69
    return {};
1✔
70
  return QByteArray{buf.GetString(), (int)buf.GetLength()};
4✔
71
}
5✔
72

73
void writeProtocolMembers(JsonWriter& stream, const QByteArray& blob)
5✔
74
{
75
  rapidjson::Document d;
5✔
76
  d.Parse(blob.data(), blob.size());
5✔
77
  if(d.HasParseError() || !d.IsObject())
5✔
78
    return;
1✔
79

80
  for(const auto& m : d.GetObject())
16✔
81
  {
82
    stream.Key(m.name.GetString(), m.name.GetStringLength());
12✔
83
    m.value.Accept(stream);
12✔
84
  }
85
}
5✔
86
}
87

88
template <>
89
SCORE_LIB_DEVICE_EXPORT void DataStreamReader::read(const Device::DeviceSettings& n)
224✔
90
{
91
  m_stream << n.name << n.protocol;
224✔
92

93
  // TODO try to see if this pattern is refactorable with the similar thing
94
  // usef for CurveSegmentData.
95

96
  auto& pl = components.interfaces<Device::ProtocolFactoryList>();
224✔
97
  auto prot = pl.get(n.protocol);
224✔
98
  if(prot)
224✔
99
  {
100
    prot->serializeProtocolSpecificSettings(n.deviceSpecificSettings, this->toVariant());
222✔
101
  }
222✔
102
  else if(!n.opaqueSettings.isEmpty())
2✔
103
  {
104
    // Deliberately not fatal: this path also runs when a document is opened,
105
    // not only when the user asks to save, so throwing would make documents
106
    // naming an unknown protocol impossible to open at all. The settings stay
107
    // in `opaqueSettings` and survive a JSON save; only the binary format
108
    // cannot carry them.
NEW
109
    qDebug() << "Warning: settings of device" << n.name << "use protocol"
×
NEW
110
             << score::uuids::toByteArray(n.protocol.impl())
×
NEW
111
             << "which is not available; they cannot be written to the binary "
×
112
                "format. Save as .score to preserve them.";
UNCOV
113
  }
×
114

115
  insertDelimiter();
224✔
116
}
224✔
117

118
template <>
119
SCORE_LIB_DEVICE_EXPORT void DataStreamWriter::write(Device::DeviceSettings& n)
41✔
120
{
121
  m_stream >> n.name >> n.protocol;
41✔
122

123
  auto& pl = components.interfaces<Device::ProtocolFactoryList>();
41✔
124
  if(auto prot = pl.get(n.protocol))
41✔
125
  {
126
    n.deviceSpecificSettings = prot->makeProtocolSpecificSettings(this->toVariant());
37✔
127
    checkDelimiter();
37✔
128
    return;
37✔
129
  }
130

131
  // No factory here. If the writer had none either it wrote no payload, so the
132
  // delimiter comes next and the round-trip is consistent -- that case has to
133
  // keep working. Otherwise the payload was written by a build that did have
134
  // the protocol, and since it carries no length prefix there is no way to skip
135
  // it: the read cannot continue.
136
  //
137
  // Telling the two apart by whether the next four bytes are the delimiter is
138
  // a guess, and a protocol whose payload happens to begin with them would be
139
  // read as having none. Nothing better is available without changing the
140
  // format, which .scorebin has no version field to migrate on; JSON, which is
141
  // what documents move between machines as, does not need any of this.
142
  int32_t next{};
4✔
143
  m_stream.stream >> next;
4✔
144
  if(next != int32_t(0xDEADBEEF))
4✔
145
    throwMissingProtocol(n);
2✔
146
}
39✔
147

148
template <>
149
SCORE_LIB_DEVICE_EXPORT void JSONReader::read(const Device::DeviceSettings& n)
79✔
150
{
151
  stream.StartObject();
79✔
152
  obj[strings.Name] = n.name;
79✔
153
  obj[strings.Protocol] = n.protocol;
79✔
154

155
  auto& pl = components.interfaces<Device::ProtocolFactoryList>();
79✔
156
  auto prot = pl.get(n.protocol);
79✔
157
  if(prot)
79✔
158
  {
159
    prot->serializeProtocolSpecificSettings(n.deviceSpecificSettings, this->toVariant());
74✔
160
  }
74✔
161
  else
162
  {
163
    writeProtocolMembers(stream, n.opaqueSettings);
5✔
164
  }
165
  stream.EndObject();
79✔
166
}
79✔
167

168
template <>
169
SCORE_LIB_DEVICE_EXPORT void JSONWriter::write(Device::DeviceSettings& n)
47✔
170
{
171
  n.name = obj[strings.Name].toString();
47✔
172
  n.protocol <<= obj[strings.Protocol];
47✔
173

174
  auto pl = components.findInterfaces<Device::ProtocolFactoryList>();
47✔
175
  if(pl)
47✔
176
  {
177
    if(auto prot = pl->get(n.protocol))
47✔
178
    {
179
      n.deviceSpecificSettings = prot->makeProtocolSpecificSettings(this->toVariant());
42✔
180
      return;
42✔
181
    }
182
  }
5✔
183

184
  n.opaqueSettings = captureProtocolMembers(base);
5✔
185
}
47✔
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