OXYGENHUB
◆ main openmsx / src / unittest / TclObject_test.cc
11.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "catch.hpp"
2
3
#include "Interpreter.hh"
4
#include "TclObject.hh"
5
6
#include <algorithm>
7
#include <cstdint>
8
#include <iterator>
9
#include <ranges>
10
#include <string>
11
12
using namespace openmsx;
13
14
TEST_CASE("TclObject, constructors")
15
{
16
	Interpreter interp;
17
	SECTION("default") {
18
		TclObject t;
19
		CHECK(t.getString() == "");
20
	}
21
	SECTION("string_view") {
22
		TclObject t1("foo"); // const char*
23
		CHECK(t1.getString() == "foo");
24
		TclObject t2(std::string("bar")); // string
25
		CHECK(t2.getString() == "bar");
26
		TclObject t3(std::string_view("qux")); // string_view
27
		CHECK(t3.getString() == "qux");
28
	}
29
	SECTION("bool") {
30
		TclObject t1(true);
31
		CHECK(t1.getString() == "1");
32
		TclObject t2(false);
33
		CHECK(t2.getString() == "0");
34
	}
35
	SECTION("int") {
36
		TclObject t(42);
37
		CHECK(t.getString() == "42");
38
	}
39
	SECTION("double") {
40
		TclObject t(6.28);
41
		CHECK(t.getString() == "6.28");
42
	}
43
	SECTION("binary") {
44
		auto buf = std::to_array<uint8_t>({'a', 'b', 'c'});
45
		TclObject t(std::span<uint8_t>{buf.data(), buf.size()});
46
		CHECK(t.getString() == "abc");
47
	}
48
	SECTION("copy") {
49
		TclObject t1("bar");
50
		TclObject t2 = t1;
51
		CHECK(t1.getString() == "bar");
52
		CHECK(t2.getString() == "bar");
53
		t1 = 123;
54
		CHECK(t1.getString() == "123");
55
		CHECK(t2.getString() == "bar");
56
	}
57
	SECTION("move") {
58
		TclObject t1("bar");
59
		TclObject t2 = std::move(t1);
60
		CHECK(t2.getString() == "bar");
61
	}
62
	SECTION("list") {
63
		TclObject t0 = makeTclList();
64
		CHECK(t0.getListLength(interp) == 0);
65
66
		TclObject t1 = makeTclList(1);
67
		CHECK(t1.getListLength(interp) == 1);
68
		CHECK(t1.getListIndex(interp, 0).getInt(interp) == 1);
69
70
		TclObject t2 = makeTclList("foo", 2.72);
71
		CHECK(t2.getListLength(interp) == 2);
72
		CHECK(t2.getListIndex(interp, 0).getString() == "foo");
73
		CHECK(t2.getListIndex(interp, 1).getDouble(interp) == 2.72);
74
	}
75
	SECTION("dict") {
76
		TclObject t0 = makeTclDict();
77
		CHECK(t0.getDictValue(interp, "foo").getString() == "");
78
		CHECK(t0.getDictValue(interp, "bar").getString() == "");
79
80
		TclObject t1 = makeTclDict("foo", true);
81
		CHECK(t1.getDictValue(interp, "foo").getBoolean(interp) == true);
82
		CHECK(t1.getDictValue(interp, "bar").getString() == "");
83
84
		TclObject t2 = makeTclDict("foo", 123, "bar", "qux");
85
		CHECK(t2.getDictValue(interp, "foo").getInt(interp) == 123);
86
		CHECK(t2.getDictValue(interp, "bar").getString() == "qux");
87
	}
88
}
89
90
TEST_CASE("TclObject, assignment")
91
{
92
	Interpreter interp;
93
	SECTION("copy") {
94
		TclObject t1(123);
95
		TclObject t2(987);
96
		REQUIRE(t1 != t2);
97
		t2 = t1;
98
		CHECK(t1 == t2);
99
		CHECK(t1.getString() == "123");
100
		CHECK(t2.getString() == "123");
101
		t1 = 456;
102
		CHECK(t1 != t2);
103
		CHECK(t1.getString() == "456");
104
		CHECK(t2.getString() == "123");
105
	}
106
	SECTION("move") {
107
		TclObject t1(123);
108
		TclObject t2(987);
109
		REQUIRE(t1 != t2);
110
		t2 = std::move(t1);
111
		CHECK(t2.getString() == "123");
112
		t1 = 456;
113
		CHECK(t1 != t2);
114
		CHECK(t1.getString() == "456");
115
		CHECK(t2.getString() == "123");
116
	}
117
}
118
119
// skipped getTclObject() / getTclObjectNonConst()
120
121
TEST_CASE("TclObject, operator=")
122
{
123
	Interpreter interp;
124
	TclObject t(123);
125
	REQUIRE(t.getString() == "123");
126
127
	SECTION("string") {
128
		t = "foo";
129
		CHECK(t.getString() == "foo");
130
		t = std::string("bar");
131
		CHECK(t.getString() == "bar");
132
		t = std::string_view("qux");
133
		CHECK(t.getString() == "qux");
134
	}
135
	SECTION("int") {
136
		t = 42;
137
		CHECK(t.getString() == "42");
138
	}
139
	SECTION("bool") {
140
		t = true;
141
		CHECK(t.getString() == "1");
142
		t = false;
143
		CHECK(t.getString() == "0");
144
	}
145
	SECTION("double") {
146
		t = -3.14;
147
		CHECK(t.getString() == "-3.14");
148
	}
149
	SECTION("binary") {
150
		std::array<uint8_t, 3> buf = {1, 2, 3};
151
		t = std::span{buf};
152
		auto result = t.getBinary(interp);
153
		CHECK(std::ranges::equal(buf, result));
154
		// 'buf' was copied into 't'
155
		CHECK(result.data() != &buf[0]);
156
		CHECK(result[0] == 1);
157
		buf[0] = 99;
158
		CHECK(result[0] == 1);
159
#if TCL_MAJOR_VERSION >= 9
160
		TclObject t2("␢");
161
		CHECK_THROWS(t2.getBinary(interp));
162
#endif
163
	}
164
	SECTION("copy") {
165
		TclObject t2(true);
166
		REQUIRE(t2.getString() == "1");
167
		t = t2;
168
		CHECK(t .getString() == "1");
169
		CHECK(t2.getString() == "1");
170
		t2 = false;
171
		CHECK(t .getString() == "1");
172
		CHECK(t2.getString() == "0");
173
	}
174
	SECTION("move") {
175
		TclObject t2(true);
176
		REQUIRE(t2.getString() == "1");
177
		t = std::move(t2);
178
		CHECK(t .getString() == "1");
179
		t2 = false;
180
		CHECK(t .getString() == "1");
181
		CHECK(t2.getString() == "0");
182
	}
183
}
184
185
TEST_CASE("TclObject, addListElement")
186
{
187
	Interpreter interp;
188
189
	SECTION("no error") {
190
		TclObject t;
191
		CHECK(t.getListLength(interp) == 0);
192
		t.addListElement("foo bar");
193
		CHECK(t.getListLength(interp) == 1);
194
		t.addListElement(false);
195
		CHECK(t.getListLength(interp) == 2);
196
		t.addListElement(33);
197
		CHECK(t.getListLength(interp) == 3);
198
		t.addListElement(9.23);
199
		CHECK(t.getListLength(interp) == 4);
200
		TclObject t2("bla");
201
		t.addListElement(t2);
202
		CHECK(t.getListLength(interp) == 5);
203
		t.addListElement(std::string("qux"));
204
		CHECK(t.getListLength(interp) == 6);
205
		auto buf = std::to_array<uint8_t>({'x', 'y', 'z'});
206
		t.addListElement(std::span<uint8_t>{buf.data(), buf.size()});
207
		CHECK(t.getListLength(interp) == 7);
208
209
		TclObject l0 = t.getListIndex(interp, 0);
210
		TclObject l1 = t.getListIndex(interp, 1);
211
		TclObject l2 = t.getListIndex(interp, 2);
212
		TclObject l3 = t.getListIndex(interp, 3);
213
		TclObject l4 = t.getListIndex(interp, 4);
214
		TclObject l5 = t.getListIndex(interp, 5);
215
		TclObject l6 = t.getListIndex(interp, 6);
216
		CHECK(l0.getString() == "foo bar");
217
		CHECK(l1.getString() == "0");
218
		CHECK(l2.getString() == "33");
219
		CHECK(l3.getString() == "9.23");
220
		CHECK(l4.getString() == "bla");
221
		CHECK(l5.getString() == "qux");
222
		CHECK(l6.getString() == "xyz");
223
224
		CHECK(t.getString() == "{foo bar} 0 33 9.23 bla qux xyz");
225
	}
226
	SECTION("error") {
227
		TclObject t("{foo"); // invalid list representation
228
		CHECK_THROWS(t.getListLength(interp));
229
		CHECK_THROWS(t.addListElement(123));
230
	}
231
}
232
233
TEST_CASE("TclObject, addListElements")
234
{
235
	Interpreter interp;
236
	std::array ints = {7, 6, 5};
237
	std::array doubles = {1.2, 5.6};
238
239
	SECTION("no error") {
240
		TclObject t;
241
		CHECK(t.getListLength(interp) == 0);
242
		// iterator-pair
243
		t.addListElements(std::begin(ints), std::end(ints));
244
		CHECK(t.getListLength(interp) == 3);
245
		CHECK(t.getListIndex(interp, 1).getString() == "6");
246
		// range (array)
247
		t.addListElements(doubles);
248
		CHECK(t.getListLength(interp) == 5);
249
		CHECK(t.getListIndex(interp, 3).getString() == "1.2");
250
		// std::views::transform
251
		t.addListElements(std::views::transform(ints, [](int i) { return 2 * i; }));
252
		CHECK(t.getListLength(interp) == 8);
253
		CHECK(t.getListIndex(interp, 7).getString() == "10");
254
		// multiple
255
		t.addListElement("one", 2, 3.14);
256
		CHECK(t.getListLength(interp) == 11);
257
		CHECK(t.getListIndex(interp, 8).getString() == "one");
258
		CHECK(t.getListIndex(interp, 9).getString() == "2");
259
		CHECK(t.getListIndex(interp, 10).getString() == "3.14");
260
	}
261
	SECTION("error") {
262
		TclObject t("{foo"); // invalid list representation
263
		CHECK_THROWS(t.addListElements(std::begin(doubles), std::end(doubles)));
264
		CHECK_THROWS(t.addListElements(ints));
265
	}
266
}
267
268
TEST_CASE("TclObject, addDictKeyValue(s)")
269
{
270
	Interpreter interp;
271
	TclObject t;
272
273
	t.addDictKeyValue("one", 1);
274
	CHECK(t.getDictValue(interp, "one").getInt(interp) == 1);
275
	CHECK(t.getDictValue(interp, "two").getString() == "");
276
	CHECK(t.getDictValue(interp, "three").getString() == "");
277
278
	t.addDictKeyValues("two", 2, "three", 3.14);
279
	CHECK(t.getDictValue(interp, "one").getInt(interp) == 1);
280
	CHECK(t.getDictValue(interp, "two").getInt(interp) == 2);
281
	CHECK(t.getDictValue(interp, "three").getDouble(interp) == 3.14);
282
283
	t.addDictKeyValues("four", false, "one", "een");
284
	CHECK(t.getDictValue(interp, "one").getString() == "een");
285
	CHECK(t.getDictValue(interp, "two").getInt(interp) == 2);
286
	CHECK(t.getDictValue(interp, "three").getDouble(interp) == 3.14);
287
	CHECK(t.getDictValue(interp, "four").getBoolean(interp) == false);
288
}
289
290
// there are no setting functions (yet?) for dicts
291
292
TEST_CASE("TclObject, getXXX")
293
{
294
	Interpreter interp;
295
	TclObject t0;
296
	TclObject t1("Off");
297
	TclObject t2(1);
298
	TclObject t3(2.71828);
299
300
	SECTION("getString") { // never fails
301
		CHECK(t0.getString() == "");
302
		CHECK(t1.getString() == "Off");
303
		CHECK(t2.getString() == "1");
304
		CHECK(t3.getString() == "2.71828");
305
	}
306
	SECTION("getInt") {
307
		CHECK_THROWS(t0.getInt(interp));
308
		CHECK_THROWS(t1.getInt(interp));
309
		CHECK       (t2.getInt(interp) == 1);
310
		CHECK_THROWS(t3.getInt(interp));
311
	}
312
	SECTION("getBoolean") {
313
		CHECK_THROWS(t0.getBoolean(interp));
314
		CHECK       (t1.getBoolean(interp) == false);
315
		CHECK       (t2.getBoolean(interp) == true);
316
		CHECK       (t3.getBoolean(interp) == true);
317
	}
318
	SECTION("getDouble") {
319
		CHECK_THROWS(t0.getDouble(interp));
320
		CHECK_THROWS(t1.getDouble(interp));
321
		CHECK       (t2.getDouble(interp) == 1.0);
322
		CHECK       (t3.getDouble(interp) == 2.71828);
323
	}
324
}
325
326
// getBinary() already tested above
327
// getListLength and getListIndex() already tested above
328
329
TEST_CASE("TclObject, getDictValue")
330
{
331
	Interpreter interp;
332
333
	SECTION("no error") {
334
		TclObject t("one 1 two 2.0 three drie");
335
		CHECK(t.getDictValue(interp, TclObject("two"  )).getString() == "2.0");
336
		CHECK(t.getDictValue(interp, TclObject("one"  )).getString() == "1");
337
		CHECK(t.getDictValue(interp, TclObject("three")).getString() == "drie");
338
		// missing key -> empty string .. can be improved when needed
339
		CHECK(t.getDictValue(interp, TclObject("four" )).getString() == "");
340
	}
341
	SECTION("invalid dict") {
342
		TclObject t("{foo");
343
		CHECK_THROWS(t.getDictValue(interp, TclObject("foo")));
344
	}
345
}
346
347
TEST_CASE("TclObject, STL interface on Tcl list")
348
{
349
	Interpreter interp;
350
351
	SECTION("empty") {
352
		TclObject t;
353
		CHECK(t.size() == 0);
354
		CHECK(t.empty() == true);
355
		CHECK(t.begin() == t.end());
356
	}
357
	SECTION("not empty") {
358
		TclObject t("1 1 2 3 5 8 13 21 34 55");
359
		CHECK(t.size() == 10);
360
		CHECK(t.empty() == false);
361
		auto b = t.begin();
362
		auto e = t.end();
363
		CHECK(std::distance(b, e) == 10);
364
		CHECK(*b == "1");
365
		std::advance(b, 5);
366
		CHECK(*b == "8");
367
		++b;
368
		CHECK(*b == "13");
369
		std::advance(b, 4);
370
		CHECK(b == e);
371
	}
372
	SECTION("invalid list") {
373
		// acts as if the list is empty .. can be improved when needed
374
		TclObject t("{foo bar qux");
375
		CHECK(t.size() == 0);
376
		CHECK(t.empty() == true);
377
		CHECK(t.begin() == t.end());
378
	}
379
}
380
381
TEST_CASE("TclObject, evalBool")
382
{
383
	Interpreter interp;
384
	CHECK(TclObject("23 == (20 + 3)").evalBool(interp) == true);
385
	CHECK(TclObject("1 >= (6-2)"    ).evalBool(interp) == false);
386
	CHECK_THROWS(TclObject("bla").evalBool(interp));
387
}
388
389
TEST_CASE("TclObject, executeCommand")
390
{
391
	Interpreter interp;
392
	CHECK(TclObject("return foobar").executeCommand(interp).getString() == "foobar");
393
	CHECK(TclObject("set n 2").executeCommand(interp).getString() == "2");
394
	TclObject cmd("string repeat bla $n");
395
	CHECK(cmd.executeCommand(interp, true).getString() == "blabla");
396
	CHECK(TclObject("incr n").executeCommand(interp).getString() == "3");
397
	CHECK(cmd.executeCommand(interp, true).getString() == "blablabla");
398
399
	CHECK_THROWS(TclObject("qux").executeCommand(interp));
400
}
401
402
TEST_CASE("TclObject, operator==, operator!=")
403
{
404
	Interpreter interp;
405
	TclObject t0;
406
	TclObject t1("foo");
407
	TclObject t2("bar qux");
408
	TclObject t3("foo");
409
410
	CHECK(  t0 == t0 ); CHECK(!(t0 != t0));
411
	CHECK(!(t0 == t1)); CHECK(  t0 != t1 );
412
	CHECK(!(t0 == t2)); CHECK(  t0 != t2 );
413
	CHECK(!(t0 == t3)); CHECK(  t0 != t3 );
414
	CHECK(  t1 == t1 ); CHECK(!(t1 != t1));
415
	CHECK(!(t1 == t2)); CHECK(  t1 != t2 );
416
	CHECK(  t1 == t3 ); CHECK(!(t1 != t3));
417
	CHECK(  t2 == t2 ); CHECK(!(t2 != t2));
418
	CHECK(!(t2 == t3)); CHECK(  t2 != t3 );
419
	CHECK(  t3 == t3 ); CHECK(!(t3 != t3));
420
421
	CHECK(t0 == ""   ); CHECK(!(t0 != ""   )); CHECK(""    == t0); CHECK(!(""    != t0));
422
	CHECK(t0 != "foo"); CHECK(!(t0 == "foo")); CHECK("foo" != t0); CHECK(!("foo" == t0));
423
	CHECK(t1 != ""   ); CHECK(!(t1 == ""   )); CHECK(""    != t1); CHECK(!(""    == t1));
424
	CHECK(t1 == "foo"); CHECK(!(t1 != "foo")); CHECK("foo" == t1); CHECK(!("foo" != t1));
425
	CHECK(t2 != ""   ); CHECK(!(t2 == ""   )); CHECK(""    != t2); CHECK(!(""    == t2));
426
	CHECK(t2 != "foo"); CHECK(!(t2 == "foo")); CHECK("foo" != t2); CHECK(!("foo" == t2));
427
}
428
429
// skipped XXTclHasher