Skip to content

Commit 3cc59de

Browse files
authored
Add typescript quickstart smoketest (#3463)
# Description of Changes Based on #3461, which fixes a bug encountered in the quickstart. # API and ABI breaking changes <!-- If this is an API or ABI breaking change, please apply the corresponding GitHub label. --> n/a # Expected complexity level and risk <!-- How complicated do you think these changes are? Grade on a scale from 1 to 5, where 1 is a trivial change, and 5 is a deep-reaching and complex change. This complexity rating applies not only to the complexity apparent in the diff, but also to its interactions with existing and future code. If you answered more than a 2, explain what is complex about the PR, and what other components it interacts with in potentially concerning ways. --> 1 # Testing <!-- Describe any testing you've done, and any testing you'd like your reviewers to do, so that you're confident that all the changes work as expected! --> - [x] Yes - [ ] <!-- maybe a test you want a reviewer to do, so they can check it off when they're satisfied. -->
1 parent a4b8abf commit 3cc59de

File tree

4 files changed

+68
-11
lines changed

4 files changed

+68
-11
lines changed

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,17 @@ jobs:
5050
- uses: actions/setup-dotnet@v4
5151
with:
5252
global-json-file: global.json
53+
54+
# nodejs and pnpm are required for the typescript quickstart smoketest
55+
- name: Set up Node.js
56+
uses: actions/setup-node@v4
57+
with:
58+
node-version: 18
59+
60+
- uses: pnpm/action-setup@v4
61+
with:
62+
run_install: true
63+
5364
- name: Install psql (Windows)
5465
if: runner.os == 'Windows'
5566
run: choco install psql -y --no-progress

smoketests/__init__.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import logging
1515
import http.client
1616
import tomllib
17+
import functools
1718

1819
# miscellaneous file paths
1920
TEST_DIR = Path(__file__).parent
@@ -28,6 +29,7 @@
2829
TEMPLATE_CARGO_TOML = open(STDB_DIR / "crates/cli/templates/basic-rust/server/Cargo.toml").read()
2930
bindings_path = (STDB_DIR / "crates/bindings").absolute()
3031
escaped_bindings_path = str(bindings_path).replace('\\', '\\\\\\\\') # double escape for re.sub + toml
32+
TYPESCRIPT_BINDINGS_PATH = (STDB_DIR / "crates/bindings-typescript").absolute()
3133
TEMPLATE_CARGO_TOML = (re.compile(r"^spacetimedb\s*=.*$", re.M) \
3234
.sub(f'spacetimedb = {{ path = "{escaped_bindings_path}", features = {{features}} }}', TEMPLATE_CARGO_TOML))
3335

@@ -170,6 +172,21 @@ def run_cmd(*args, capture_stderr=True, check=True, full_output=False, cmd_name=
170172
output.check_returncode()
171173
return output if full_output else output.stdout
172174

175+
@functools.cache
176+
def pnpm_path():
177+
pnpm = shutil.which("pnpm")
178+
if not pnpm:
179+
raise Exception("pnpm not installed")
180+
return pnpm
181+
182+
def pnpm(*args, **kwargs):
183+
return run_cmd(pnpm_path(), *args, **kwargs)
184+
185+
@functools.cache
186+
def build_typescript_sdk():
187+
pnpm("install", cwd=TYPESCRIPT_BINDINGS_PATH)
188+
pnpm("build", cwd=TYPESCRIPT_BINDINGS_PATH)
189+
173190
def spacetime(*args, **kwargs):
174191
return run_cmd(SPACETIME_BIN, *args, cmd_name="spacetime", **kwargs)
175192

smoketests/docker.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,21 @@ def restart_docker():
2626

2727
# Ensure all nodes are running.
2828
attempts = 0
29-
while attempts < 5:
29+
while attempts < 10:
3030
attempts += 1
31-
if all(container.is_running(docker, spacetimedb_ping_url) for container in containers):
31+
containers_alive = {
32+
container.name: container.is_running(docker, spacetimedb_ping_url)
33+
for container in containers
34+
}
35+
if all(containers_alive.values()):
3236
# sleep a bit more to allow for leader election etc
3337
# TODO: make ping endpoint consider all server state
3438
time.sleep(2)
3539
return
3640
else:
3741
time.sleep(1)
3842

39-
raise Exception("Not all containers are up and running")
43+
raise Exception(f"Not all containers are up and running: {containers_alive!r}")
4044

4145
def spacetimedb_ping_url(port: int) -> str:
4246
return f"http://127.0.0.1:{port}/v1/ping"

smoketests/tests/quickstart.py

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import xmltodict
77

88
import smoketests
9-
from .. import Smoketest, STDB_DIR, run_cmd, TEMPLATE_CARGO_TOML
9+
from .. import Smoketest, STDB_DIR, run_cmd, TEMPLATE_CARGO_TOML, TYPESCRIPT_BINDINGS_PATH, build_typescript_sdk, pnpm
1010

1111

1212
def _write_file(path: Path, content: str):
@@ -19,12 +19,13 @@ def _append_to_file(path: Path, content: str):
1919
f.write(content)
2020

2121

22-
def _parse_quickstart(doc_path: Path, language: str) -> str:
22+
def _parse_quickstart(doc_path: Path, language: str, module_name: str) -> str:
2323
"""Extract code blocks from `quickstart.md` docs.
2424
This will replicate the steps in the quickstart guide, so if it fails the quickstart guide is broken.
2525
"""
2626
content = Path(doc_path).read_text()
27-
blocks = re.findall(rf"```{language}\n(.*?)\n```", content, re.DOTALL)
27+
codeblock_lang = "ts" if language == "typescript" else language
28+
blocks = re.findall(rf"```{codeblock_lang}\n(.*?)\n```", content, re.DOTALL)
2829

2930
end = ""
3031
if language == "csharp":
@@ -42,7 +43,7 @@ def _parse_quickstart(doc_path: Path, language: str) -> str:
4243
filtered_blocks.append(block)
4344
blocks = filtered_blocks
4445
# So we could have a different db for each language
45-
return "\n".join(blocks).replace("quickstart-chat", f"quickstart-chat-{language}") + end
46+
return "\n".join(blocks).replace("quickstart-chat", module_name) + end
4647

4748
def load_nuget_config(p: Path):
4849
if p.exists():
@@ -101,6 +102,8 @@ class BaseQuickstart(Smoketest):
101102
MODULE_CODE = ""
102103

103104
lang = None
105+
client_lang = None
106+
codeblock_langs = None
104107
server_doc = None
105108
client_doc = None
106109
server_file = None
@@ -118,12 +121,16 @@ def project_init(self, path: Path):
118121
def sdk_setup(self, path: Path):
119122
raise NotImplementedError
120123

124+
@property
125+
def _module_name(self):
126+
return f"quickstart-chat-{self.lang}"
127+
121128
def _publish(self) -> Path:
122129
base_path = Path(self.enterClassContext(tempfile.TemporaryDirectory()))
123130
server_path = base_path / "server"
124131

125132
self.generate_server(server_path)
126-
self.publish_module(f"quickstart-chat-{self.lang}", capture_stderr=True, clear=True)
133+
self.publish_module(self._module_name, capture_stderr=True, clear=True)
127134
return base_path / "client"
128135

129136
def generate_server(self, server_path: Path):
@@ -141,7 +148,7 @@ def generate_server(self, server_path: Path):
141148
)
142149
self.project_path = server_path / "spacetimedb"
143150
shutil.copy2(STDB_DIR / "rust-toolchain.toml", self.project_path)
144-
_write_file(self.project_path / self.server_file, _parse_quickstart(self.server_doc, self.lang))
151+
_write_file(self.project_path / self.server_file, _parse_quickstart(self.server_doc, self.lang, self._module_name))
145152
self.server_postprocess(self.project_path)
146153
self.spacetime("build", "-d", "-p", self.project_path, capture_stderr=True)
147154

@@ -163,13 +170,14 @@ def _test_quickstart(self):
163170

164171
run_cmd(*self.build_cmd, cwd=client_path, capture_stderr=True)
165172

173+
client_lang = self.client_lang or self.lang
166174
self.spacetime(
167-
"generate", "--lang", self.lang,
175+
"generate", "--lang", client_lang,
168176
"--out-dir", client_path / self.module_bindings,
169177
"--project-path", self.project_path, capture_stderr=True
170178
)
171179
# Replay the quickstart guide steps
172-
main = _parse_quickstart(self.client_doc, self.lang)
180+
main = _parse_quickstart(self.client_doc, client_lang, self._module_name)
173181
for src, dst in self.replacements.items():
174182
main = main.replace(src, dst)
175183
main += "\n" + self.extra_code
@@ -322,3 +330,20 @@ def test_quickstart(self):
322330
if not smoketests.HAVE_DOTNET:
323331
self.skipTest("C# SDK requires .NET to be installed.")
324332
self._test_quickstart()
333+
334+
# We use the Rust client for testing the TypeScript server quickstart because
335+
# the TypeScript client quickstart is a React app, which is difficult to
336+
# smoketest.
337+
class TypeScript(Rust):
338+
lang = "typescript"
339+
client_lang = "rust"
340+
server_doc = STDB_DIR / "docs/docs/06-Server Module Languages/05-typescript-quickstart.md"
341+
server_file = "src/index.ts"
342+
343+
def server_postprocess(self, server_path: Path):
344+
build_typescript_sdk()
345+
pnpm("install", TYPESCRIPT_BINDINGS_PATH, cwd=server_path)
346+
347+
def test_quickstart(self):
348+
"""Run the TypeScript quickstart guides for server."""
349+
self._test_quickstart()

0 commit comments

Comments
 (0)