glib 0.10.3

Rust bindings for the GLib library
Documentation
import json
import os
import subprocess
import sys


TEST_FILENAME = "tmp_py_file"
TEST_FOLDER = "clone_tests"
TESTS = [
    ("clone!( => move || {})",
        "If you have nothing to clone, no need to use this macro!"),
    ("clone!(|| {})",
        "If you have nothing to clone, no need to use this macro!"),
    ("clone!(|a, b| {})",
        "If you have nothing to clone, no need to use this macro!"),
    ("clone!(@weak a, @weak b => |x| {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a, @weak b => || {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a, @weak b => |x| println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a, @weak b => || println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a => |x| {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a => || {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a => |x| println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak a => || println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@strong self => move |x| {})",
        "Can't use `self` as variable name. Try storing it in a temporary variable or rename it using `as`."),
    ("clone!(@strong self.v => move |x| {})",
        "Field accesses are not allowed as is, you must rename it!"),
    ("clone!(@weak v => @default-return false, || {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak v => @default-return false, || println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak v => @default-return false, |bla| {})",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak v => @default-return false, |bla| println!(\"a\"))",
        "Closure needs to be \"moved\" so please add `move` before closure"),
    ("clone!(@weak v => default-return false, move || {})",
        "Missing `@` before `default-return`"),
    ("clone!(@weak v => @default-return false move || {})",
        "Missing comma after `@default-return`'s value"),
    ("clone!(@yolo v => move || {})",
        "Unknown keyword \"yolo\", only `weak`, `weak-allow-none` and `strong` are allowed"),
    ("clone!(v => move || {})",
        "You need to specify if this is a weak or a strong clone."),
    ("clone!(@strong v => async move {println!(\"foo\");});",
        "async blocks are not supported by the clone! macro"),
    ("clone!(@strong v => {println!(\"foo\");});",
        "Missing `move` and closure declaration"),
    ("clone!(@strong v, @default-return lol => move || {println!(\"foo\");});",
        "`@default-return` should be after `=>`"),
    ("clone!(@default-return lol, @strong v => move || {println!(\"foo\");});",
        "`@default-return` should be after `=>`"),
]


def convert_to_string(s):
    if s.__class__.__name__ == 'bytes':
        return s.decode('utf-8')
    return s


def exec_command(command):
    child = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    stdout, stderr = child.communicate()
    return (child.returncode == 0, convert_to_string(stdout), convert_to_string(stderr))


def run_test(code, expected_str):
    with open("{}/{}.rs".format(TEST_FOLDER, TEST_FILENAME), 'w') as f:
        f.write('extern crate glib;use glib::clone;use std::rc::Rc;fn main(){{let v = Rc::new(1);{};}}'.format(code))
    code, stdout, stderr = exec_command([
        "bash",
        "-c",
        "cd {} && cargo build --message-format json".format(TEST_FOLDER),
    ])
    os.remove("{}/{}.rs".format(TEST_FOLDER, TEST_FILENAME))
    if code is True:
        return "This isn't supposed to compile!"
    parts = stdout.split('}\n{')
    compiler_message = None
    for (pos, part) in enumerate(parts):
        try:
            if pos > 0:
                part = "{" + part
            if pos + 1 < len(parts):
                part += "}"
            x = json.loads(part)
            if (x["reason"] != "compiler-message"
                or x["message"]["message"] == "aborting due to previous error"):
                continue
            compiler_message = x["message"]["message"]
            break
        except Exception:
            continue
    if compiler_message is None:
        return "Weird issue: no compiler-message found..."
    if expected_str == "":
        return "failed: `{}`".format(compiler_message)
    if expected_str not in compiler_message:
        return "`{}` not found in `{}`".format(expected_str, compiler_message)
    return None


def run_tests():
    print("About to start the tests on the clone! macro.")
    print("It might be slow to run the first one since cargo has to build dependencies...")
    print("")
    errors = 0
    with open('{}/Cargo.toml'.format(TEST_FOLDER), 'w') as f:
        f.write("""[package]
name = "test"
version = "0.0.1"
authors = ["gtk-rs developers"]

[dependencies]
glib = {{ path = ".." }}

[[bin]]
name = "{0}"
path = "{0}.rs"
""".format(TEST_FILENAME))
    for (code, expected_str) in TESTS:
        sys.stdout.write('Running `{}`...'.format(code))
        sys.stdout.flush()
        err = run_test(code, expected_str)
        if err is not None:
            print(" FAILED\n{}".format(err))
            errors += 1
        else:
            print(" OK")
    print("Ran {} tests, got {} failure{}".format(len(TESTS), errors, "s" if errors > 1 else ""))
    os.remove("{}/Cargo.toml".format(TEST_FOLDER))
    os.remove("{}/Cargo.lock".format(TEST_FOLDER))
    exec_command(['bash', '-c', 'rm -r {}/target'.format(TEST_FOLDER)])
    return errors


if __name__ == "__main__":
    sys.exit(run_tests())