
DocsetTest.pudu
Pudu129 lines8.7 KB
1/** @Docgen.Docset.Tests — projects load from disk and publish incrementally */2module PuduLangDocgen.DocsetTest34import Std.Bytes as Bytes5import Std.Env as Env6import Std.Fs as Fs7import Std.Io as Io8import Std.Path as Path9import Std.Test as Test10import PuduLangDocgen.Build as Build11import PuduLangDocgen.Command.Arguments as Arguments12import PuduLangDocgen.Command as Command13import PuduLangDocgen.Constants.Codes as Codes14import PuduLangDocgen as Docgen15import PuduLangDocgen.Docset as Docset16import PuduLangDocgen.Docset.Tasks as Tasks17import PuduLangDocgen.Docset.Tools as Tools18import PuduLangDocgen.Docset.Walk as Walk19import PuduLangDocgen.Scaffold as Scaffold20import PuduLangDocgen.Serve as Serve212223const PROJECT: Array[(Str, Str)] = [24 ("docgen.json", "\{\"build\": \{\"content\": [\{\"files\": [\"**/*.md\", \"**/toc.yml\"], \"src\": \"docs\"\}], \"resource\": [\{\"files\": \"*.png\", \"src\": \"docs\"\}]\}\}"),25 ("docs/index.md", "# Home\n\n[!INCLUDE[note](shared/note.md)]\n\n"),26 ("docs/other.md", "# Other"),27 ("docs/shared/note.md", "Shared note."),28 ("docs/logo.png", "PNG"),29 ("docs/.draft.md", "# Hidden")30]313233fn write(root: Str) -> Bool {34 for (path, text) in PROJECT {35 let target = Path.join(root, path)36 let _folder = Io.makeDirectory(Path.directoryOf(target))37 match Io.write(target, text) {38 case Ok(_) => {}39 case Err(_) => { return false }40 }41 }42 true43}444546fn built(config: Str, chosen: &Docset.Options) -> Docgen.Report {47 match Docset.build(config, chosen, &Build.extensions()) {48 case Ok(report) => report49 case Err(problems) => Docgen.Report{written: problems.map(|problem: Docgen.Diagnostic| problem.code + " " + problem.message), unchanged: [], removed: [], diagnostics: []}50 }51}525354fn parsed(arguments: Array[Str]) -> Str {55 match Arguments.parse(&arguments) {56 case Ok(found) => found.command + "|" + found.positional.join(",") + "|" + found.flags.join(",") + "|" + found.values.map(fn(entry: (Str, Str)) -> Str {57 let (name, value) = entry58 name + "=" + value59 }).join(",")60 case Err(reason) => reason61 }62}636465fn main() -> Int {66 let root = match Fs.temporaryDirectoryIn(Env.temporaryDirectory(), "docgen-test") {67 case Ok(found) => found68 case Err(_) => ""69 }70 let ready = !root.isEmpty() && write(root)71 let config = Path.join(root, "docgen.json")72 let quiet = Docset.Options{..Docset.options(), disableGitFeatures: true}73 let first = built(config, &quiet)74 let second = built(config, &quiet)75 let forced = built(config, &Docset.Options{..quiet, force: true})76 let _gone = Io.remove(Path.join(root, "docs/other.md"))77 let third = built(config, &quiet)78 let dry = built(config, &Docset.Options{..quiet, dryRun: true, output: "preview"})79 let site = Path.join(root, "_site")80 let checks = Test.suite("Docset", &[81 Test.that("project written", ready),82 Test.equals("first build writes everything", &first.written.length(), &11),83 Test.equals("second build writes nothing", &(second.written, second.unchanged.length()), &([], 11)),84 Test.equals("force rewrites", &forced.written.length(), &11),85 Test.that("stale outputs removed", third.removed.contains("other.html") && !Io.exists(Path.join(site, "other.html"))),86 Test.that("include resolved from disk", match Io.read(Path.join(site, "index.html")) { case Ok(text) => text.contains("Shared note.") case Err(_) => false }),87 Test.that("resource copied", Io.exists(Path.join(site, "logo.png"))),88 Test.that("hidden files skipped", match Walk.files(root, &[]) { case Ok(paths) => !paths.contains("docs/.draft.md") && paths.contains("docs/other.md") == false case Err(_) => false }),89 Test.that("dry run writes nothing", dry.written.isEmpty() && !dry.unchanged.isEmpty() && !Io.exists(Path.join(root, "preview"))),90 Test.equals("missing configuration", &built(Path.join(root, "none.json"), &quiet).written.length(), &1),91 Test.that("state kept beside the configuration", Io.exists(Path.join(root, ".docgen/build.json"))),92 Test.equals("command parsing", &[parsed(["build", "site", "-o", "out", "--force"]), parsed(["serve", "_site", "--port", "9000"]), parsed(["--dryRun"])], &["build|site|--force|--output=out", "serve|_site||--port=9000", "||--dryRun|"]),93 Test.equals("command refusals", &[parsed(["--bogus"]), parsed(["build", "-o"]), parsed(["a", "b"])], &["unknown option --bogus", "option -o needs a value", "expected at most 1 path, found a b"]),94 Test.equals("help and version", &[Command.run(&["help"]), Command.run(&["version"]), Command.run(&["--nope"])], &[0, 0, 2]),95 Test.equals("request targets", &["/", "/guide/", "/a%20b.html", "/x.html?q=1", "/../secret", "/%zz"].map(|path: Str| Serve.target(path)), &[Some("index.html"), Some("guide/index.html"), Some("a b.html"), Some("x.html"), None, None]),96 Test.equals("served pages", &[Serve.answer(site, "/index.html").status, Serve.answer(site, "/missing.html").status], &[200, 404]),97 Test.that("not found page served", match Bytes.toText(&Serve.answer(site, "/missing.html").body) { case Ok(text) => text.contains("Page not found") case Err(_) => false }),98 Test.equals("request lines", &["GET /index.html HTTP/1.1", "HEAD / HTTP/1.1", "POST / HTTP/1.1", "GET /", "", "GET / FTP/1"].map(|line: Str| Serve.reply(site, line).status), &[200, 200, 405, 400, 400, 400]),99 Test.equals("media types", &[Serve.answer(site, "/index.html").media.startsWith("text/html"), Serve.answer(site, "/nowhere/").media.startsWith("text/html")], &[true, true]),100 Test.that("response head", Serve.head(&Serve.Reply{status: 404, media: "text/plain", body: Bytes.fromText("abc")}) == "HTTP/1.1 404 Not Found\r\nContent-Type: text/plain\r\nContent-Length: 3\r\nCache-Control: no-cache\r\nConnection: close\r\n\r\n")101 ])102 let fresh = Path.join(root, "fresh")103 let created = match Tasks.init(fresh, &Scaffold.defaults("Fresh")) { case Ok(found) => found case Err(_) => [] }104 let again = match Tasks.init(fresh, &Scaffold.defaults("Fresh")) { case Ok(_) => "created" case Err(found) => found[0].message }105 let exported = match Tasks.exportTemplate(Path.join(root, "tpl")) { case Ok(found) => found.length() case Err(_) => 0 }106 let merged = match Tasks.merge(&[Path.join(site, "xrefmap.yml"), Path.join(site, "xrefmap.yml")], Path.join(root, "all.yml")) { case Ok(count) => count case Err(_) => -1 }107 let freshBuild = built(Path.join(fresh, "docgen.json"), &quiet)108 let mode = match Fs.permissions(Path.join(site, "index.html")) { case Ok(found) => found.readable case Err(_) => false }109 let (served, serviceProblems) = Tools.consult(&["http://127.0.0.1:1/xref?uid=\{uid\}"], &["A", "B"])110 let extra = Test.suite("Docset tasks", &[111 Test.equals("unreachable service answers nothing and is asked once", &(served, serviceProblems.map(|problem: Docgen.Diagnostic| (problem.code, problem.path))), &([], [(Codes.XREF_UNAVAILABLE, "http://127.0.0.1:1/xref?uid=\{uid\}")])),112 Test.equals("no services asked without identities", &Tools.consult(&["http://127.0.0.1:1/\{uid\}"], &[]), &([], [])),113 Test.equals("scaffold files", &created, &["docgen.json", "index.md", "toc.yml", "docs/introduction.md", "docs/getting-started.md", "docs/toc.yml", ".gitignore", "src/Example.pudu"]),114 Test.equals("existing project refused", &again, &"a project already exists here"),115 Test.equals("scaffold builds without warnings", &(freshBuild.written.length() > 10, freshBuild.diagnostics), &(true, [])),116 Test.equals("template export", &exported, &14),117 Test.equals("merge keeps each identity once", &merged, &0),118 Test.that("published files readable", mode),119 Test.that("no staging files left", !Io.exists(Path.join(site, "index.html.docgen-partial"))),120 Test.equals("options with long names", &[parsed(["build", "-l", "log.json", "--logLevel", "warning", "--verbose", "--open-browser", "--theme", "dark", "-a"])], &["build||--verbose,--open-browser,--all|--log=log.json,--logLevel=warning,--theme=dark"]),121 Test.equals("bad log level", &Command.run(&["build", "--logLevel", "loud"]), &2),122 Test.equals("new commands parse", &[parsed(["init", "site", "--yes"]), parsed(["template", "export", "out"]), parsed(["merge", "a.yml", "b.yml", "-o", "c.yml"]), parsed(["pdf", "site"])], &["init|site|--yes|", "template|export,out||", "merge|a.yml,b.yml||--output=c.yml", "pdf|site||"])123 ])124 let _cleaned = Fs.removeTree(root)125 let ran = Test.run(&Test.group("Docset", &[checks, extra]))126 for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }127 Test.report(&ran)128}129