# BST-TIMEOUT: 600 # 74 cases, nearly all of them a process spawn. MEASURED at 49s # (==BST-SUITE editorconfig-core-c elapsed=49s, twice), which the 60s # default clears by 11s -- close enough that a loaded host trips it. . "$BST_LIB" # editorconfig-core-c 0.12.6 -- meta-oe's editorconfig-core-c # (meta-openembedded/meta-oe/recipes-devtools/editorconfig): the reference C # implementation of EditorConfig. The port ships libeditorconfig_static.a and # the two public headers, and it ships `editorconfig` -- the command-line query # tool that resolves a path against the .editorconfig files above it. The tool # is what this suite drives, because it exercises the whole library (the ini # parser, the upward walk, the property merge and the glob engine) and it is # what a user of this image actually has. # # --------------------------------------------------------------------------- # WHY THE GLOB CASES ARE THE HEART OF THIS SUITE # # editorconfig-core-c has exactly one dependency, pcre2, and exactly one # consumer of it: ec_glob.c, which translates an EditorConfig section pattern # into a PCRE2 pattern, compiles it and matches the path against it. Nothing # else in the library links pcre2. So the glob cases are not extra coverage on # top of the port -- they ARE the assertion that the pcre2 this image ships is # linked and working on this arm. A port whose ini parsing was perfect and # whose pcre2 was broken would answer every simple `[*]` case correctly and # fail only here. # # EVERY EXPECTATION CAME OFF A NATIVE x86_64 CONTROL -- the identical 0.12.6 # tree at the recipe's own SRCREV, configured with the same cmake line the port # uses, built and driven through this exact fixture before this suite shipped. # That is where the four non-obvious ones come from, none of which a reading of # the EditorConfig spec would have produced: # # 1. tab_width IS SYNTHESIZED. It appears in every answer that has an # indent_size, though no .editorconfig here declares it once. # 2. AN `unset` PROPERTY IS PRINTED, as the literal value `unset` -- it does # not disappear from the answer. A suite asserting its absence would have # shipped a permanent red. # 3. `indent_style = tab` SYNTHESIZES `indent_size = tab`, so the deepest # fixture answers with a key its .editorconfig never mentions. # 4. A PATH THAT DOES NOT EXIST IS STILL ANSWERED, rc=0. The tool resolves a # path, it does not open a file, and it never stats one. # # The version-compat arm (-b) is what pins 1 and 3 as version-gated rather than # unconditional: under `-b 0.8.0` the same deepest file answers with # indent_style alone. # --------------------------------------------------------------------------- R="${BST_EC_ROOT:-}" BIN=$R/usr/bin/editorconfig ARCHIVE=$R/usr/lib/libeditorconfig_static.a if [ ! -x "$BIN" ] && [ ! -f "$ARCHIVE" ]; then bst_skip editorconfig/present "editorconfig-core-c not installed on this image" exit 0 fi bst_ok editorconfig/present # --- 1. the package is whole ------------------------------------------------- # # The recipe inherits plain cmake rather than lib_package, so the tool lands in # ${PN} while the headers go to -dev and the archive to -staticdev. All three # are named in the image, so all three are asserted. check editorconfig/bin test -x "$BIN" check editorconfig/header test -f "$R/usr/include/editorconfig/editorconfig.h" check editorconfig/header-hnd test -f "$R/usr/include/editorconfig/editorconfig_handle.h" check editorconfig/staticlib test -f "$ARCHIVE" check editorconfig/pkgconfig test -f "$R/usr/lib/pkgconfig/editorconfig.pc" # Upstream sets the VERSION property on the executable target, so cmake installs # `editorconfig-0.12.6` and leaves `editorconfig` as a symlink to it. Both halves # are asserted: a rootfs that flattened the symlink, or one that dropped the # versioned file, still answers every query above through whichever half survived. check editorconfig/bin-versioned test -f "$R/usr/bin/editorconfig-0.12.6" if [ -L "$R/usr/bin/editorconfig" ]; then bst_ok editorconfig/bin-symlink else bst_notok editorconfig/bin-symlink "/usr/bin/editorconfig is not a symlink to the versioned binary"; fi # Shape, not absence: this arm converts to shared libraries per recipe # (BRINTOS-1740), so a .so here is a conversion, not a policy breach. bst_lib_shape editorconfig/no-shared "$R/usr/lib/libeditorconfig.so" "$R/usr/lib/libeditorconfig.so.0" # --- 2. the tool answers at all ---------------------------------------------- check_out editorconfig/version "EditorConfig C Core Version 0.12.6" "$BIN" --version check_out editorconfig/help "Usage:" "$BIN" --help # --- 3. the fixture ---------------------------------------------------------- # # Built here rather than shipped in data/, because what is under test is the # upward walk: the answer depends on where each file sits relative to the # .editorconfig files above it, and a fixture staged by the recipe would be # read-only and at a path the walk cannot be reasoned about. work=${TMPDIR:-/tmp}/bst-editorconfig.$$ rm -rf "$work" mkdir -p "$work/tree/sub/deep" "$work/plain" "$work/alt" "$work/glob/lib" "$work/glob/a/b/c" \ || { bst_notok editorconfig/workdir "cannot create $work"; exit 0; } cat > "$work/tree/.editorconfig" <<'EOT' root = true [*] indent_style = space indent_size = 4 end_of_line = lf charset = utf-8 insert_final_newline = true trim_trailing_whitespace = true [*.py] indent_size = 8 [*.{js,ts}] indent_style = tab [Makefile] indent_style = tab indent_size = 8 [*.md] trim_trailing_whitespace = unset EOT # No `root` key: the walk must CONTINUE past this file to the one above, which # is the whole of what tree/sub asserts. cat > "$work/tree/sub/.editorconfig" <<'EOT' [*] indent_size = 2 EOT # `root = true`: the walk must STOP here, so nothing from tree/.editorconfig # reaches tree/sub/deep. cat > "$work/tree/sub/deep/.editorconfig" <<'EOT' root = true [*] indent_style = tab EOT cat > "$work/alt/.ecalt" <<'EOT' root = true [*] indent_style = tab indent_size = 3 EOT cat > "$work/glob/.editorconfig" <<'EOT' root = true [**/deep.txt] indent_size = 11 [*.{c,h}] indent_size = 16 [lib/*.c] indent_size = 12 [[abc]file.txt] indent_size = 13 [[!x]other.txt] indent_size = 14 [page{1..3}.md] indent_size = 15 [?ingle.txt] indent_size = 17 EOT for f in a.txt main.py app.js lib.ts README.md Makefile; do : > "$work/tree/$f"; done : > "$work/tree/sub/b.txt" : > "$work/tree/sub/deep/c.txt" : > "$work/plain/plain.txt" : > "$work/alt/x.c" for f in afile.txt yother.txt xother.txt page2.md page9.md hdr.h single.txt; do : > "$work/glob/$f"; done : > "$work/glob/lib/z.c" : > "$work/glob/a/b/c/deep.txt" # val -> the value the tool printed for , empty when it # printed no such key. Absence and a wrong value are different failures and the # case detail has to be able to tell them apart, so this returns the empty # string rather than failing. val() { awk -F= -v k="$2" '$1 == k { print $2; exit }' "$1" } # nkeys -> how many properties the tool printed. nkeys() { awk 'NF { n++ } END { print n + 0 }' "$1" } # ask -> run the tool, capturing stdout+stderr. ask() { _o=$1; shift "$BIN" "$@" > "$_o" 2>&1 } # --- 4. the property merge, top of the tree ---------------------------------- # # One [*] section and four narrowing ones. Each file below is answered by a # different combination of them, and the values are compared key by key so a # single wrong property names itself rather than hiding inside one diff. o=$work/out ask "$o" "$work/tree/a.txt"; rc=$? check_eq editorconfig/plain-rc 0 "$rc" check_eq editorconfig/plain-style space "$(val "$o" indent_style)" check_eq editorconfig/plain-size 4 "$(val "$o" indent_size)" check_eq editorconfig/plain-eol lf "$(val "$o" end_of_line)" check_eq editorconfig/plain-charset utf-8 "$(val "$o" charset)" check_eq editorconfig/plain-final-nl true "$(val "$o" insert_final_newline)" check_eq editorconfig/plain-trim true "$(val "$o" trim_trailing_whitespace)" # Never declared anywhere in the fixture -- the library derives it from # indent_size, and that derivation is a property of the port, not of the file. check_eq editorconfig/plain-tab-width 4 "$(val "$o" tab_width)" check_eq editorconfig/plain-nkeys 7 "$(nkeys "$o")" # [*.py] overrides indent_size only; everything else still comes from [*], and # the synthesized tab_width follows the OVERRIDDEN value rather than the [*] one. ask "$o" "$work/tree/main.py" check_eq editorconfig/py-size 8 "$(val "$o" indent_size)" check_eq editorconfig/py-tab-width 8 "$(val "$o" tab_width)" check_eq editorconfig/py-style space "$(val "$o" indent_style)" check_eq editorconfig/py-eol lf "$(val "$o" end_of_line)" # A brace pattern reaching two extensions. Both files take indent_style from it # and keep indent_size from [*], which is the merge working in both directions. ask "$o" "$work/tree/app.js" check_eq editorconfig/brace-js-style tab "$(val "$o" indent_style)" check_eq editorconfig/brace-js-size 4 "$(val "$o" indent_size)" ask "$o" "$work/tree/lib.ts" check_eq editorconfig/brace-ts-style tab "$(val "$o" indent_style)" check_eq editorconfig/brace-ts-size 4 "$(val "$o" indent_size)" # A section that is a literal filename, not a pattern. ask "$o" "$work/tree/Makefile" check_eq editorconfig/literal-style tab "$(val "$o" indent_style)" check_eq editorconfig/literal-size 8 "$(val "$o" indent_size)" # THE UNSET PROPERTY IS PRINTED, as the literal value `unset`. It does not # vanish from the answer, and the key count is unchanged -- both halves are # asserted, because a library that dropped the key instead would still satisfy # any test that only looked for the absence of `true`. ask "$o" "$work/tree/README.md" check_eq editorconfig/unset-value unset "$(val "$o" trim_trailing_whitespace)" check_eq editorconfig/unset-nkeys 7 "$(nkeys "$o")" check_eq editorconfig/unset-others true "$(val "$o" insert_final_newline)" # --- 5. the upward walk ------------------------------------------------------ # # tree/sub/.editorconfig has no `root`, so the walk continues past it: the # child's indent_size wins AND the parent's other properties still arrive. # Failing to continue and failing to override are opposite bugs, and one case # each would not distinguish them -- so both are asserted on the same file. ask "$o" "$work/tree/sub/b.txt" check_eq editorconfig/nested-override 2 "$(val "$o" indent_size)" check_eq editorconfig/nested-inherited space "$(val "$o" indent_style)" check_eq editorconfig/nested-eol lf "$(val "$o" end_of_line)" check_eq editorconfig/nested-tab-width 2 "$(val "$o" tab_width)" # tree/sub/deep/.editorconfig sets `root = true`, so the walk STOPS there and # nothing from the two files above reaches this path. The proof is in what is # ABSENT: end_of_line, charset and the rest are declared only above the stop. ask "$o" "$work/tree/sub/deep/c.txt" check_eq editorconfig/root-stop-style tab "$(val "$o" indent_style)" check_eq editorconfig/root-stop-eol "" "$(val "$o" end_of_line)" check_eq editorconfig/root-stop-charset "" "$(val "$o" charset)" check_eq editorconfig/root-stop-trim "" "$(val "$o" trim_trailing_whitespace)" # `indent_style = tab` with no indent_size synthesizes `indent_size = tab`, so # the answer carries exactly two keys -- one of which the file never mentions. check_eq editorconfig/root-stop-size tab "$(val "$o" indent_size)" check_eq editorconfig/root-stop-nkeys 2 "$(nkeys "$o")" # A path under no .editorconfig at all: no properties and NO ERROR. The ticket # names this case, and it is the one an implementation is most likely to turn # into a diagnostic. ask "$o" "$work/plain/plain.txt"; rc=$? check_eq editorconfig/outside-rc 0 "$rc" check_eq editorconfig/outside-nkeys 0 "$(nkeys "$o")" # --- 6. the glob engine, which is the pcre2 linkage -------------------------- ask "$o" "$work/glob/a/b/c/deep.txt" check_eq editorconfig/glob-doublestar 11 "$(val "$o" indent_size)" ask "$o" "$work/glob/hdr.h" check_eq editorconfig/glob-brace-ext 16 "$(val "$o" indent_size)" # lib/z.c matches BOTH [*.{c,h}] and the later [lib/*.c]; the later section # wins. This is one case doing two jobs: a path-relative pattern matches at all, # and section order decides when two of them do. ask "$o" "$work/glob/lib/z.c" check_eq editorconfig/glob-path-relative 12 "$(val "$o" indent_size)" ask "$o" "$work/glob/afile.txt" check_eq editorconfig/glob-charclass 13 "$(val "$o" indent_size)" # A negated character class, asserted from both sides: the file it must match # and the one it must not. Only the pair rules out a pattern that matches # everything. ask "$o" "$work/glob/yother.txt" check_eq editorconfig/glob-negclass-hit 14 "$(val "$o" indent_size)" ask "$o" "$work/glob/xother.txt" check_eq editorconfig/glob-negclass-miss "" "$(val "$o" indent_size)" # The numeric range {1..3} -- the one pattern form that is compiled by a # SECOND pcre2 pattern inside ec_glob.c rather than translated directly, so it # fails independently of every other glob here. ask "$o" "$work/glob/page2.md" check_eq editorconfig/glob-numrange-hit 15 "$(val "$o" indent_size)" ask "$o" "$work/glob/page9.md" check_eq editorconfig/glob-numrange-miss "" "$(val "$o" indent_size)" ask "$o" "$work/glob/single.txt" check_eq editorconfig/glob-question 17 "$(val "$o" indent_size)" # --- 7. the command-line surface --------------------------------------------- # # Several files in one invocation switch the output to a [path]-headed block # per file, which is a different code path in main.c from the single-file form. ask "$o" "$work/tree/a.txt" "$work/tree/main.py" check_out editorconfig/multi-header-1 "[$work/tree/a.txt]" cat "$o" check_out editorconfig/multi-header-2 "[$work/tree/main.py]" cat "$o" check_eq editorconfig/multi-nkeys 14 "$(awk '/^\[/ { next } NF { n++ } END { print n + 0 }' "$o")" # `-` reads the paths from stdin and answers in the same headed form. printf '%s\n' "$work/tree/a.txt" | "$BIN" - > "$o" 2>&1; rc=$? check_eq editorconfig/stdin-rc 0 "$rc" check_out editorconfig/stdin-header "[$work/tree/a.txt]" cat "$o" check_eq editorconfig/stdin-size 4 "$(val "$o" indent_size)" # -f names a conf file other than .editorconfig. The same directory answers # nothing under the default name, which is what makes the pair meaningful. ask "$o" -f .ecalt "$work/alt/x.c" check_eq editorconfig/conf-name-style tab "$(val "$o" indent_style)" check_eq editorconfig/conf-name-size 3 "$(val "$o" indent_size)" ask "$o" "$work/alt/x.c" check_eq editorconfig/conf-name-default-empty 0 "$(nkeys "$o")" # A relative path is REFUSED, with a diagnostic and rc=1. The tool resolves # nothing against the current directory. ( cd "$work/tree" && "$BIN" a.txt ) > "$o" 2>&1; rc=$? check_eq editorconfig/relative-rc 1 "$rc" check_out editorconfig/relative-msg "Input file must be a full path name." cat "$o" # A path that does not exist is ANSWERED, rc=0: the tool resolves a path, it # never opens or stats the file. Pinned because it looks like a bug and is not. ask "$o" "$work/tree/nosuch.txt"; rc=$? check_eq editorconfig/absent-path-rc 0 "$rc" check_eq editorconfig/absent-path-size 4 "$(val "$o" indent_size)" # No arguments at all: usage, rc=1. check_rc editorconfig/no-args 1 "$BIN" # An unparseable -b argument is rejected by name. "$BIN" -b 9.9.9.9 "$work/tree/a.txt" > "$o" 2>&1; rc=$? check_eq editorconfig/bad-version-rc 1 "$rc" check_out editorconfig/bad-version-msg "Invalid version number: 9.9.9.9" cat "$o" # THE VERSION-COMPAT ARM. Under -b 0.8.0 the indent_size=tab synthesis of # section 5 does not happen, so the same file answers with one key instead of # two. This is what establishes that synthesis as version-gated behaviour the # port carries correctly, rather than as an accident of this build. ask "$o" -b 0.8.0 "$work/tree/sub/deep/c.txt" check_eq editorconfig/compat-0.8.0-style tab "$(val "$o" indent_style)" check_eq editorconfig/compat-0.8.0-size "" "$(val "$o" indent_size)" check_eq editorconfig/compat-0.8.0-nkeys 1 "$(nkeys "$o")" # ...and under 0.9.0 it does, which is the other side of the same gate. ask "$o" -b 0.9.0 "$work/tree/sub/deep/c.txt" check_eq editorconfig/compat-0.9.0-size tab "$(val "$o" indent_size)" rm -rf "$work"