|
| 1 | +import logging |
| 2 | +import re |
| 3 | +import tempfile |
| 4 | +from pathlib import Path |
| 5 | + |
| 6 | +from .. import Smoketest, MODULES_DIR, STDB_DIR, run_cmd, TEMPLATE_CARGO_TOML, TEST_DIR |
| 7 | + |
| 8 | +DEPENDENCIES = """ |
| 9 | +log = "0.4" |
| 10 | +hex= "0.4" |
| 11 | +""" |
| 12 | +sdk_path = (STDB_DIR / "crates/sdk").absolute() |
| 13 | +escaped_sdk_path = str(sdk_path).replace('\\', '\\\\\\\\') # double escape for re.sub + toml |
| 14 | +DEPENDENCIES_TOML = f'spacetimedb-sdk = {{ path = "{escaped_sdk_path}" }}' + DEPENDENCIES |
| 15 | + |
| 16 | +# The quickstart `main.rs` use a `repl` loop to read user input, so we need to replace it for the smoketest. |
| 17 | +TEST = """ |
| 18 | +fn user_input_direct(ctx: &DbConnection) { |
| 19 | + let mut line = String::new(); |
| 20 | + std::io::stdin().read_line(&mut line).expect("Failed to read from stdin."); |
| 21 | + if let Some(name) = line.strip_prefix("/name ") { |
| 22 | + ctx.reducers.set_name(name.to_string()).unwrap(); |
| 23 | + } else { |
| 24 | + ctx.reducers.send_message(line).unwrap(); |
| 25 | + } |
| 26 | + std::thread::sleep(std::time::Duration::from_secs(1)); |
| 27 | + std::process::exit(0); |
| 28 | +} |
| 29 | +""" |
| 30 | + |
| 31 | + |
| 32 | +def parse_quickstart(): |
| 33 | + """This method is used to parse the documentation of `docs/docs/sdks/rust/quickstart.md`.""" |
| 34 | + |
| 35 | + doc_path = STDB_DIR / "docs/docs/sdks/rust" / "quickstart.md" |
| 36 | + content = open(doc_path, "r").read() |
| 37 | + |
| 38 | + # Extract all Rust code blocks from the documentation. |
| 39 | + # This will replicate the steps in the quickstart guide, so if it fails the quickstart guide is broken. |
| 40 | + code_blocks = re.findall(r"```rust\n(.*?)\n```", content, re.DOTALL) |
| 41 | + |
| 42 | + return "\n".join(code_blocks).replace("user_input_loop(&ctx)", "user_input_direct(&ctx)") + "\n" + TEST |
| 43 | + |
| 44 | + |
| 45 | +class Quickstart(Smoketest): |
| 46 | + AUTOPUBLISH = False |
| 47 | + MODULE_CODE = "" |
| 48 | + |
| 49 | + def check(self, input_cmd: str, client_path: Path, contains: str): |
| 50 | + output = run_cmd("cargo", "run", input=input_cmd, cwd=client_path, capture_stderr=True, text=True) |
| 51 | + self.assertIn(contains, output) |
| 52 | + |
| 53 | + def test_quickstart_rs(self): |
| 54 | + """This test is designed to run the quickstart guide for the Rust SDK.""" |
| 55 | + self.project_path = MODULES_DIR / "quickstart-chat" |
| 56 | + self.config_path = self.project_path / "config.toml" |
| 57 | + self.publish_module("quickstart-chat", capture_stderr=True, clear=True) |
| 58 | + client_path = Path(self.enterClassContext(tempfile.TemporaryDirectory())) |
| 59 | + logging.info(f"Generating client code in {client_path}...") |
| 60 | + # Create a cargo project structure |
| 61 | + run_cmd( |
| 62 | + "cargo", "new", "--bin", "quickstart_chat_client", |
| 63 | + cwd=client_path, capture_stderr=True |
| 64 | + ) |
| 65 | + client_path = client_path / "quickstart_chat_client" |
| 66 | + |
| 67 | + open(client_path / "Cargo.toml", "a").write(DEPENDENCIES_TOML) |
| 68 | + |
| 69 | + # Replay the quickstart guide steps |
| 70 | + main = parse_quickstart() |
| 71 | + open(client_path / "src" / "main.rs", "w").write(main) |
| 72 | + self.spacetime( |
| 73 | + "generate", |
| 74 | + "--lang", "rust", |
| 75 | + "--out-dir", client_path / "src" / "module_bindings", |
| 76 | + "--project-path", self.project_path, |
| 77 | + capture_stderr=True |
| 78 | + ) |
| 79 | + logging.info(f"Client code generated in {client_path}.") |
| 80 | + run_cmd( |
| 81 | + "cargo", "build", |
| 82 | + cwd=client_path, capture_stderr=True |
| 83 | + ) |
| 84 | + |
| 85 | + # Replay the quickstart guide steps for test the client |
| 86 | + self.check("", client_path, "connected") |
| 87 | + self.check("/name Alice", client_path, "Alice") |
| 88 | + self.check("Hello World", client_path, "Hello World") |
0 commit comments