Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-docgen

Documentation publishing for Pudu: articles, API references, navigation, search, and static websites

0.1.0Apache-2.01

InstallClose

DocsetTest.pudu

Pudu129 lines8.7 KB

GitHub ↗
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 Serve2122/// Files of the project written for these checks.23const 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![logo](logo.png)"),26  ("docs/other.md", "# Other"),27  ("docs/shared/note.md", "Shared note."),28  ("docs/logo.png", "PNG"),29  ("docs/.draft.md", "# Hidden")30]3132/// Writes the project under a folder.33fn 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}4445/// A build report, or an empty one with the failure codes as written paths.46fn 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}5253/// Parsed command-line parts, or the refusal.54fn 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}6364/// Runs every loading, publishing, command, and serving contract.65fn 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