Skip to content

Commit 761cbe8

Browse files
authored
Merge pull request #607 from scratchcpp/block_test_api
Add block test API
2 parents c748bad + 273a6c2 commit 761cbe8

File tree

12 files changed

+573
-0
lines changed

12 files changed

+573
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ if (LIBSCRATCHCPP_USE_LLVM)
7777
include/scratchcpp/dev/executablecode.h
7878
include/scratchcpp/dev/executioncontext.h
7979
include/scratchcpp/dev/promise.h
80+
include/scratchcpp/dev/test/scriptbuilder.h
8081
)
8182

8283
if(LIBSCRATCHCPP_PRINT_LLVM_IR)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#pragma once
4+
5+
#include <vector>
6+
7+
#include "../../global.h"
8+
#include "../../spimpl.h"
9+
10+
namespace libscratchcpp
11+
{
12+
13+
class IExtension;
14+
class IEngine;
15+
class Target;
16+
class List;
17+
18+
} // namespace libscratchcpp
19+
20+
namespace libscratchcpp::test
21+
{
22+
23+
class ScriptBuilderPrivate;
24+
25+
/*! \brief The ScriptBuilder class is used to build Scratch scripts in unit tests. */
26+
class LIBSCRATCHCPP_EXPORT ScriptBuilder
27+
{
28+
public:
29+
ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target);
30+
ScriptBuilder(const ScriptBuilder &) = delete;
31+
32+
~ScriptBuilder();
33+
34+
void addBlock(const std::string &opcode);
35+
void addReporterBlock(const std::string &opcode);
36+
void captureBlockReturnValue();
37+
38+
void addValueInput(const std::string &name, const Value &value);
39+
void addNullInput(const std::string &name);
40+
41+
void addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock);
42+
void addNullObscuredInput(const std::string &name);
43+
44+
void addDropdownInput(const std::string &name, const std::string &selectedValue);
45+
void addDropdownField(const std::string &name, const std::string &selectedValue);
46+
47+
void build();
48+
void run();
49+
50+
List *capturedValues() const;
51+
52+
private:
53+
void addBlock(std::shared_ptr<Block> block);
54+
55+
spimpl::unique_impl_ptr<ScriptBuilderPrivate> impl;
56+
};
57+
58+
} // namespace libscratchcpp::test

src/dev/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
add_subdirectory(blocks)
22
add_subdirectory(engine)
3+
add_subdirectory(test)

src/dev/test/CMakeLists.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
target_sources(scratchcpp
2+
PRIVATE
3+
scriptbuilder.cpp
4+
scriptbuilder_p.cpp
5+
scriptbuilder_p.h
6+
)

src/dev/test/scriptbuilder.cpp

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#include <scratchcpp/dev/test/scriptbuilder.h>
4+
#include <scratchcpp/block.h>
5+
#include <scratchcpp/input.h>
6+
#include <scratchcpp/field.h>
7+
#include <scratchcpp/dev/compilerconstant.h>
8+
#include <scratchcpp/dev/executablecode.h>
9+
#include <scratchcpp/stage.h>
10+
#include <scratchcpp/iengine.h>
11+
#include <scratchcpp/list.h>
12+
13+
#include "scriptbuilder_p.h"
14+
15+
using namespace libscratchcpp;
16+
using namespace libscratchcpp::test;
17+
18+
static std::unordered_map<IEngine *, std::shared_ptr<List>> captureLists;
19+
20+
/*! Constructs ScriptBuilder. */
21+
ScriptBuilder::ScriptBuilder(IExtension *extension, IEngine *engine, std::shared_ptr<Target> target) :
22+
impl(spimpl::make_unique_impl<ScriptBuilderPrivate>(engine, target))
23+
{
24+
// Create capture list
25+
if (captureLists.find(engine) != captureLists.cend()) {
26+
std::cerr << "error: only one ScriptBuilder can be created for each engine" << std::endl;
27+
return;
28+
}
29+
30+
captureLists[engine] = std::make_shared<List>("", "");
31+
32+
// Add start hat block
33+
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "script_builder_init");
34+
engine->addCompileFunction(extension, block->opcode(), [](Compiler *compiler) -> CompilerValue * {
35+
compiler->engine()->addGreenFlagScript(compiler->block());
36+
return nullptr;
37+
});
38+
addBlock(block);
39+
40+
// Add compile function for return value capture block
41+
engine->addCompileFunction(extension, "script_builder_capture", [](Compiler *compiler) -> CompilerValue * {
42+
CompilerValue *input = compiler->addInput("VALUE");
43+
compiler->createListAppend(captureLists[compiler->engine()].get(), input);
44+
return nullptr;
45+
});
46+
}
47+
48+
/*! Destroys ScriptBuilder. */
49+
ScriptBuilder::~ScriptBuilder()
50+
{
51+
captureLists.erase(impl->engine);
52+
}
53+
54+
/*! Adds a block with the given opcode to the script. */
55+
void ScriptBuilder::addBlock(const std::string &opcode)
56+
{
57+
impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
58+
addBlock(impl->lastBlock);
59+
}
60+
61+
/*! Creates a reporter block with the given opcode to be used with captureBlockReturnValue() later. */
62+
void ScriptBuilder::addReporterBlock(const std::string &opcode)
63+
{
64+
impl->lastBlock = std::make_shared<Block>(std::to_string(impl->blockId++), opcode);
65+
}
66+
67+
/*! Captures the return value of the created reporter block. It can be retrieved using capturedValues() later. */
68+
void ScriptBuilder::captureBlockReturnValue()
69+
{
70+
if (!impl->lastBlock)
71+
return;
72+
73+
auto valueBlock = impl->lastBlock;
74+
addBlock("script_builder_capture");
75+
addObscuredInput("VALUE", valueBlock);
76+
}
77+
78+
/*! Adds a simple input with a value to the current block. */
79+
void ScriptBuilder::addValueInput(const std::string &name, const Value &value)
80+
{
81+
if (!impl->lastBlock)
82+
return;
83+
84+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
85+
input->setPrimaryValue(value);
86+
impl->lastBlock->addInput(input);
87+
}
88+
89+
/*! Adds a null input (zero) to the current block. */
90+
void ScriptBuilder::addNullInput(const std::string &name)
91+
{
92+
if (!impl->lastBlock)
93+
return;
94+
95+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
96+
impl->lastBlock->addInput(input);
97+
}
98+
99+
/*! Adds an input obscured by the given block to the current block. */
100+
void ScriptBuilder::addObscuredInput(const std::string &name, std::shared_ptr<Block> valueBlock)
101+
{
102+
if (!impl->lastBlock)
103+
return;
104+
105+
valueBlock->setId(std::to_string(impl->blockId++));
106+
impl->inputBlocks.push_back(valueBlock);
107+
108+
auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
109+
input->setValueBlock(valueBlock);
110+
impl->lastBlock->addInput(input);
111+
}
112+
113+
/*! Adds an input obscured by a block which returns zero to the current block. */
114+
void ScriptBuilder::addNullObscuredInput(const std::string &name)
115+
{
116+
if (!impl->lastBlock)
117+
return;
118+
119+
auto input = std::make_shared<Input>(name, Input::Type::ObscuredShadow);
120+
auto block = std::make_shared<Block>(std::to_string(impl->blockId++), "");
121+
block->setCompileFunction([](Compiler *compiler) -> CompilerValue * { return compiler->addConstValue(Value()); });
122+
input->setValueBlock(block);
123+
impl->inputBlocks.push_back(block);
124+
impl->blocks.back()->addInput(input);
125+
}
126+
127+
/*! Adds a dropdown menu input to the current block. */
128+
void ScriptBuilder::addDropdownInput(const std::string &name, const std::string &selectedValue)
129+
{
130+
if (!impl->lastBlock)
131+
return;
132+
133+
auto block = impl->blocks.back();
134+
auto input = std::make_shared<Input>(name, Input::Type::Shadow);
135+
block->addInput(input);
136+
137+
auto menu = std::make_shared<Block>(std::to_string(impl->blockId++), block->opcode() + "_menu");
138+
menu->setShadow(true);
139+
impl->inputBlocks.push_back(menu);
140+
input->setValueBlock(menu);
141+
142+
auto field = std::make_shared<Field>(name, selectedValue);
143+
menu->addField(field);
144+
}
145+
146+
/*! Adds a dropdown field to the current block. */
147+
void ScriptBuilder::addDropdownField(const std::string &name, const std::string &selectedValue)
148+
{
149+
if (!impl->lastBlock)
150+
return;
151+
152+
auto field = std::make_shared<Field>(name, selectedValue);
153+
impl->blocks.back()->addField(field);
154+
}
155+
156+
/*! Builds and compiles the script. */
157+
void ScriptBuilder::build()
158+
{
159+
if (impl->target->blocks().empty()) {
160+
for (auto block : impl->blocks)
161+
impl->target->addBlock(block);
162+
163+
for (auto block : impl->inputBlocks)
164+
impl->target->addBlock(block);
165+
}
166+
167+
std::vector<std::shared_ptr<Target>> targets = impl->engine->targets();
168+
169+
if (std::find(targets.begin(), targets.end(), impl->target) == targets.end()) {
170+
targets.push_back(impl->target);
171+
impl->engine->setTargets({ impl->target });
172+
}
173+
174+
impl->engine->compile();
175+
}
176+
177+
/*! Runs the built script. */
178+
void ScriptBuilder::run()
179+
{
180+
impl->engine->run();
181+
}
182+
183+
/*! Returns the list of captured block return values. */
184+
List *ScriptBuilder::capturedValues() const
185+
{
186+
return captureLists[impl->engine].get();
187+
}
188+
189+
void ScriptBuilder::addBlock(std::shared_ptr<Block> block)
190+
{
191+
if (!impl->blocks.empty()) {
192+
auto lastBlock = impl->blocks.back();
193+
lastBlock->setNext(block);
194+
block->setParent(lastBlock);
195+
}
196+
197+
impl->blocks.push_back(block);
198+
}

src/dev/test/scriptbuilder_p.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#include "scriptbuilder_p.h"
4+
5+
using namespace libscratchcpp;
6+
using namespace libscratchcpp::test;
7+
8+
ScriptBuilderPrivate::ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target) :
9+
engine(engine),
10+
target(target)
11+
{
12+
}

src/dev/test/scriptbuilder_p.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#pragma once
4+
5+
#include <memory>
6+
#include <vector>
7+
8+
namespace libscratchcpp
9+
{
10+
11+
class IEngine;
12+
class Target;
13+
class Block;
14+
class List;
15+
16+
} // namespace libscratchcpp
17+
18+
namespace libscratchcpp::test
19+
{
20+
21+
class ScriptBuilderPrivate
22+
{
23+
public:
24+
ScriptBuilderPrivate(IEngine *engine, std::shared_ptr<Target> target);
25+
ScriptBuilderPrivate(const ScriptBuilderPrivate &) = delete;
26+
27+
IEngine *engine = nullptr;
28+
std::shared_ptr<Target> target;
29+
std::shared_ptr<Block> lastBlock;
30+
std::vector<std::shared_ptr<Block>> blocks;
31+
std::vector<std::shared_ptr<Block>> inputBlocks;
32+
unsigned int blockId = 0;
33+
};
34+
35+
} // namespace libscratchcpp::test

test/dev/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ add_subdirectory(executioncontext)
33
add_subdirectory(llvm)
44
add_subdirectory(compiler)
55
add_subdirectory(promise)
6+
add_subdirectory(test_api)

test/dev/test_api/CMakeLists.txt

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
add_library(
2+
test_api_test_deps SHARED
3+
testextension.cpp
4+
testextension.h
5+
)
6+
7+
target_link_libraries(
8+
test_api_test_deps
9+
GTest::gtest_main
10+
scratchcpp
11+
)
12+
13+
add_executable(
14+
test_api_test
15+
scriptbuilder_test.cpp
16+
)
17+
18+
target_link_libraries(
19+
test_api_test
20+
GTest::gtest_main
21+
scratchcpp
22+
test_api_test_deps
23+
)
24+
25+
gtest_discover_tests(test_api_test)

0 commit comments

Comments
 (0)