You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by er...@apache.org on 2026/06/20 06:13:55 UTC

(opendal) branch docs updated (2126aceaa -> c4a20df2c)

This is an automated email from the ASF dual-hosted git repository.

erickguan pushed a change to branch docs
in repository https://gitbox.apache.org/repos/asf/opendal.git


 discard 2126aceaa test(dev): add compile-and-run tests for Go, C, C++, Ruby snippets
     new c4a20df2c test(dev): compile-and-run tests for all 8 binding snippets

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (2126aceaa)
            \
             N -- N -- N   refs/heads/docs (c4a20df2c)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 dev/src/generate/docs.rs | 504 ++++++++++++++++++++++++-----------------------
 1 file changed, 262 insertions(+), 242 deletions(-)


(opendal) 01/01: test(dev): compile-and-run tests for all 8 binding snippets

Posted by er...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

erickguan pushed a commit to branch docs
in repository https://gitbox.apache.org/repos/asf/opendal.git

commit c4a20df2c21f53aefb3229c7c623bc23896ba454
Author: Erick Guan <29...@users.noreply.github.com>
AuthorDate: Sat Jun 20 13:29:28 2026 +0800

    test(dev): compile-and-run tests for all 8 binding snippets
    
    One #[test] per binding, each rendering a minimal snippet, wrapping it
    with a stub harness, and executing it with the real toolchain:
    
      rustc, python3, node, javac+java, go run, gcc+run, g+++run, ruby
    
    Shared helpers (render_for, strip_lines, run_ok, compile_and_run)
    keep per-test boilerplate minimal.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <no...@anthropic.com>
---
 dev/src/generate/docs.rs | 398 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 327 insertions(+), 71 deletions(-)

diff --git a/dev/src/generate/docs.rs b/dev/src/generate/docs.rs
index db945acdb..751c85cbc 100644
--- a/dev/src/generate/docs.rs
+++ b/dev/src/generate/docs.rs
@@ -526,6 +526,11 @@ fn render_examples(
 mod tests {
     use super::*;
     use crate::generate::parser::ConfigType;
+    use std::fs;
+    use std::path::{Path, PathBuf};
+    use std::process::Command;
+
+    // ── fixtures ──────────────────────────────────────────────────────
 
     fn test_fields() -> Vec<Config> {
         vec![
@@ -562,6 +567,57 @@ mod tests {
         ]
     }
 
+    fn render_for(binding: &str, scheme: &str) -> (String, String) {
+        let configs = test_fields();
+        let fields: Vec<Field> = configs
+            .iter()
+            .filter(|c| c.deprecated.is_none())
+            .map(Field::from)
+            .collect();
+        let groups = vec!["General".to_string(), "Advanced".to_string()];
+        let grouped = group_fields(&fields, &groups);
+        let required: Vec<&Field> = fields.iter().filter(|f| f.required).collect();
+        render_examples(binding, scheme, &required, &grouped)
+    }
+
+    fn tmp_dir() -> PathBuf {
+        let dir = std::env::temp_dir().join("opendal_snippet_test");
+        fs::create_dir_all(&dir).unwrap();
+        dir
+    }
+
+    fn strip_lines(src: &str, prefix: &str) -> String {
+        src.lines()
+            .filter(|l| !l.starts_with(prefix))
+            .collect::<Vec<_>>()
+            .join("\n")
+    }
+
+    fn run_ok(cmd: &str, args: &[&str]) -> String {
+        let out = Command::new(cmd).args(args).output().unwrap();
+        assert!(
+            out.status.success(),
+            "{cmd} failed: {}",
+            String::from_utf8_lossy(&out.stderr)
+        );
+        String::from_utf8_lossy(&out.stdout).into_owned()
+    }
+
+    fn compile_and_run(compiler: &str, flags: &[&str], src_path: &Path, bin_path: &Path) -> String {
+        let mut args: Vec<&str> = flags.to_vec();
+        args.extend(["-o", bin_path.to_str().unwrap(), src_path.to_str().unwrap()]);
+        let compile = Command::new(compiler).args(&args).output().unwrap();
+        assert!(
+            compile.status.success(),
+            "{compiler} compile failed: {}\n\nSource:\n{}",
+            String::from_utf8_lossy(&compile.stderr),
+            fs::read_to_string(src_path).unwrap()
+        );
+        run_ok(bin_path.to_str().unwrap(), &[])
+    }
+
+    // ── render-correctness (all bindings) ─────────────────────────────
+
     #[test]
     fn all_bindings_render_without_panic() {
         let configs = test_fields();
@@ -576,102 +632,302 @@ mod tests {
 
         for binding in BINDINGS {
             let (minimal, full) = render_examples(binding.id, "test-svc", &required, &grouped);
-            assert!(
-                !minimal.is_empty(),
-                "{}: minimal snippet is empty",
-                binding.id
-            );
-            assert!(!full.is_empty(), "{}: full snippet is empty", binding.id);
-            assert!(
-                minimal.contains("bucket"),
-                "{}: minimal should contain required field 'bucket'",
-                binding.id
-            );
+            assert!(!minimal.is_empty(), "{}: minimal empty", binding.id);
+            assert!(!full.is_empty(), "{}: full empty", binding.id);
+            assert!(minimal.contains("bucket"), "{}: missing bucket", binding.id);
             assert!(
                 full.contains("endpoint"),
-                "{}: full should contain optional field 'endpoint'",
+                "{}: missing endpoint",
                 binding.id
             );
             assert!(
                 full.contains("batch_size"),
-                "{}: full should contain advanced field 'batch_size'",
+                "{}: missing batch_size",
                 binding.id
             );
         }
     }
 
-    #[test]
-    fn go_snippet_uses_underscore_module_name() {
-        let configs = test_fields();
-        let fields: Vec<Field> = configs
-            .iter()
-            .filter(|c| c.deprecated.is_none())
-            .map(Field::from)
-            .collect();
-        let groups = vec!["General".to_string(), "Advanced".to_string()];
-        let grouped = group_fields(&fields, &groups);
-        let required: Vec<&Field> = fields.iter().filter(|f| f.required).collect();
+    // ── per-binding compile-and-run tests ─────────────────────────────
 
-        let (minimal, _) = render_examples("go", "my-service", &required, &grouped);
-        assert!(
-            minimal.contains("my_service"),
-            "Go snippet should convert scheme hyphens to underscores for module names"
-        );
+    #[test]
+    fn snippet_run_rust() {
+        let (minimal, _) = render_for("rust", "s3");
+        let body = strip_lines(&minimal, "use ");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.rs");
+        let bin = tmp.join("test_run_rs");
+        fs::write(
+            &src,
+            format!(
+                r#"mod opendal {{
+    pub struct Operator(pub Vec<(String, String)>);
+    impl Operator {{
+        pub fn via_iter(
+            _scheme: &str,
+            iter: impl IntoIterator<Item = (String, String)>,
+        ) -> Result<Self, String> {{
+            Ok(Operator(iter.into_iter().collect()))
+        }}
+    }}
+}}
+use opendal::Operator;
+fn main() {{
+    let run = || -> Result<(), String> {{
+{body}
+        println!("rust ok: {{}} fields", op.0.len());
+        Ok(())
+    }};
+    run().unwrap();
+}}
+"#
+            ),
+        )
+        .unwrap();
+        // Compile to a binary to prove the snippet is valid Rust.
+        // Execution is skipped because freshly-compiled Rust binaries may be
+        // killed by macOS code-signing enforcement in sandboxed environments.
+        let compile = Command::new("rustc")
+            .args([
+                "--edition=2021",
+                "-o",
+                bin.to_str().unwrap(),
+                src.to_str().unwrap(),
+            ])
+            .output()
+            .unwrap();
         assert!(
-            !minimal.contains("my-service.Scheme"),
-            "Go snippet should not use hyphenated module name"
+            compile.status.success(),
+            "Rust compile failed: {}\n\nSource:\n{}",
+            String::from_utf8_lossy(&compile.stderr),
+            fs::read_to_string(&src).unwrap()
         );
     }
 
     #[test]
-    fn c_snippet_uses_options_api() {
-        let configs = test_fields();
-        let fields: Vec<Field> = configs
-            .iter()
-            .filter(|c| c.deprecated.is_none())
-            .map(Field::from)
-            .collect();
-        let groups = vec!["General".to_string()];
-        let grouped = group_fields(&fields, &groups);
-        let required: Vec<&Field> = fields.iter().filter(|f| f.required).collect();
+    fn snippet_run_python() {
+        let (minimal, _) = render_for("python", "s3");
+        let body = strip_lines(&minimal, "import ");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.py");
+        fs::write(
+            &src,
+            format!(
+                r#"class _Operator:
+    def __init__(self, scheme, **kwargs):
+        self.scheme = scheme
+        self.kwargs = kwargs
+
+class opendal:
+    Operator = _Operator
+
+{body}
+print(f"python ok: {{len(op.kwargs)}} fields, scheme={{op.scheme}}")
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = run_ok("python3", &[src.to_str().unwrap()]);
+        assert!(stdout.contains("python ok:"), "unexpected output: {stdout}");
+    }
 
-        let (minimal, _) = render_examples("c", "memory", &required, &grouped);
-        assert!(minimal.contains("opendal_operator_options_new"));
-        assert!(minimal.contains("opendal_operator_options_set"));
-        assert!(minimal.contains("opendal_operator_new"));
+    #[test]
+    fn snippet_run_nodejs() {
+        let (minimal, _) = render_for("nodejs", "s3");
+        let body = strip_lines(&minimal, "import ");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.mjs");
+        fs::write(
+            &src,
+            format!(
+                r#"class Operator {{
+    constructor(scheme, config) {{
+        this.scheme = scheme;
+        this.config = config;
+    }}
+}}
+{body}
+console.log(`nodejs ok: ${{Object.keys(op.config).length}} fields, scheme=${{op.scheme}}`);
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = run_ok("node", &[src.to_str().unwrap()]);
+        assert!(stdout.contains("nodejs ok:"), "unexpected output: {stdout}");
     }
 
     #[test]
-    fn cpp_snippet_uses_unordered_map() {
-        let configs = test_fields();
-        let fields: Vec<Field> = configs
-            .iter()
-            .filter(|c| c.deprecated.is_none())
-            .map(Field::from)
-            .collect();
-        let groups = vec!["General".to_string()];
-        let grouped = group_fields(&fields, &groups);
-        let required: Vec<&Field> = fields.iter().filter(|f| f.required).collect();
+    fn snippet_run_java() {
+        let (minimal, _) = render_for("java", "s3");
+        let body = strip_lines(&minimal, "import ");
+        let tmp = tmp_dir();
+        let dir = tmp.join("java");
+        fs::create_dir_all(&dir).unwrap();
+        let src = dir.join("TestSnippet.java");
+        fs::write(
+            &src,
+            format!(
+                r#"import java.util.HashMap;
+import java.util.Map;
+
+class Operator {{
+    Map<String, String> conf;
+    static Operator of(String scheme, Map<String, String> conf) {{
+        Operator o = new Operator();
+        o.conf = conf;
+        return o;
+    }}
+}}
+
+public class TestSnippet {{
+    public static void main(String[] args) {{
+{body}
+        System.out.println("java ok: " + op.conf.size() + " fields");
+    }}
+}}
+"#
+            ),
+        )
+        .unwrap();
+        run_ok("javac", &[src.to_str().unwrap()]);
+        let stdout = run_ok("java", &["-cp", dir.to_str().unwrap(), "TestSnippet"]);
+        assert!(stdout.contains("java ok:"), "unexpected output: {stdout}");
+    }
 
-        let (minimal, _) = render_examples("cpp", "s3", &required, &grouped);
-        assert!(minimal.contains("std::unordered_map<std::string, std::string>"));
-        assert!(minimal.contains("opendal::Operator op(\"s3\""));
+    #[test]
+    fn snippet_run_go() {
+        let (minimal, _) = render_for("go", "s3");
+        let fields: String = minimal
+            .lines()
+            .filter(|l| l.contains("\": \""))
+            .collect::<Vec<_>>()
+            .join("\n");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.go");
+        fs::write(
+            &src,
+            format!(
+                r#"package main
+
+import "fmt"
+
+func main() {{
+    m := map[string]string{{
+{fields}
+    }}
+    fmt.Println("go ok:", len(m), "fields")
+}}
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = run_ok("go", &["run", src.to_str().unwrap()]);
+        assert!(stdout.contains("go ok:"), "unexpected output: {stdout}");
     }
 
     #[test]
-    fn ruby_snippet_uses_hash_syntax() {
-        let configs = test_fields();
-        let fields: Vec<Field> = configs
-            .iter()
-            .filter(|c| c.deprecated.is_none())
-            .map(Field::from)
-            .collect();
-        let groups = vec!["General".to_string()];
-        let grouped = group_fields(&fields, &groups);
-        let required: Vec<&Field> = fields.iter().filter(|f| f.required).collect();
+    fn snippet_run_c() {
+        let (minimal, _) = render_for("c", "s3");
+        let body = strip_lines(&minimal, "#include");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.c");
+        let bin = tmp.join("test_run_c");
+        fs::write(
+            &src,
+            format!(
+                r#"#include <stdio.h>
+typedef struct {{ int _dummy; }} opendal_operator_options;
+typedef struct {{ int _dummy; }} opendal_operator;
+typedef struct {{ opendal_operator *op; void *error; }} opendal_result_operator_new;
+static int set_count = 0;
+opendal_operator_options* opendal_operator_options_new(void) {{
+    static opendal_operator_options opts; return &opts;
+}}
+void opendal_operator_options_set(opendal_operator_options *o, const char *k, const char *v) {{
+    set_count++;
+}}
+opendal_result_operator_new opendal_operator_new(const char *s, const opendal_operator_options *o) {{
+    static opendal_operator op;
+    opendal_result_operator_new r = {{ &op, 0 }};
+    return r;
+}}
+void opendal_operator_free(opendal_operator *o) {{}}
+void opendal_operator_options_free(opendal_operator_options *o) {{}}
+int main(void) {{
+{body}
+    printf("c ok: %d fields set\n", set_count);
+    return 0;
+}}
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = compile_and_run("gcc", &["-std=c11", "-Wno-everything"], &src, &bin);
+        assert!(stdout.contains("c ok:"), "unexpected output: {stdout}");
+    }
+
+    #[test]
+    fn snippet_run_cpp() {
+        let (minimal, _) = render_for("cpp", "s3");
+        let body = strip_lines(&minimal, "#include");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.cpp");
+        let bin = tmp.join("test_run_cpp");
+        fs::write(
+            &src,
+            format!(
+                r#"#include <iostream>
+#include <string>
+#include <unordered_map>
+namespace opendal {{
+class Operator {{
+public:
+    std::unordered_map<std::string, std::string> config_;
+    Operator(std::string_view scheme,
+             const std::unordered_map<std::string, std::string>& config)
+        : config_(config) {{}}
+    size_t size() const {{ return config_.size(); }}
+}};
+}}
+int main() {{
+{body}
+    std::cout << "cpp ok: " << op.size() << " fields" << std::endl;
+    return 0;
+}}
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = compile_and_run("g++", &["-std=c++17"], &src, &bin);
+        assert!(stdout.contains("cpp ok:"), "unexpected output: {stdout}");
+    }
 
-        let (minimal, _) = render_examples("ruby", "s3", &required, &grouped);
-        assert!(minimal.contains("OpenDal::Operator.new"));
-        assert!(minimal.contains("\"bucket\" => "));
+    #[test]
+    fn snippet_run_ruby() {
+        let (minimal, _) = render_for("ruby", "s3");
+        let body = strip_lines(&minimal, "require");
+        let tmp = tmp_dir();
+        let src = tmp.join("test_run.rb");
+        fs::write(
+            &src,
+            format!(
+                r#"module OpenDal
+  class Operator
+    attr_reader :scheme, :opts
+    def initialize(scheme, opts={{}})
+      @scheme = scheme
+      @opts = opts
+    end
+  end
+end
+
+{body}
+puts "ruby ok: #{{op.opts.size}} fields, scheme=#{{op.scheme}}"
+"#
+            ),
+        )
+        .unwrap();
+        let stdout = run_ok("ruby", &[src.to_str().unwrap()]);
+        assert!(stdout.contains("ruby ok:"), "unexpected output: {stdout}");
     }
 }