branch: externals/matlab-mode
commit 3bcbfd2d7580077b4728846c17b910ab7b4e1cdd
Merge: 8193e3d7ec 76d0de7782
Author: John Ciolfi <[email protected]>
Commit: GitHub <[email protected]>
Merge pull request #64 from mathworks/matlab-ts-mode
test: enable reuse of some tests for matlab tree-sitter
---
matlab--access.el | 4 +-
tests/README-TEST-MATLAB-TREE-SITTER.org | 208 +++++++++++++++++++++++++++++++
tests/sweep-test-matlab-ts-grammar.el | 25 ++--
tests/sweep-test-matlab-ts-grammar.sh | 33 ++---
tests/t-utils.el | 37 ++++--
tests/test-matlab-ts-mode-parser.el | 7 +-
tests/test-matlab-ts-mode-parser.sh | 21 ++++
tests/test-tree-sitter-utils.sh | 93 ++++++++++++++
8 files changed, 381 insertions(+), 47 deletions(-)
diff --git a/matlab--access.el b/matlab--access.el
index 474281b772..e4fdbb698f 100644
--- a/matlab--access.el
+++ b/matlab--access.el
@@ -138,8 +138,8 @@ is found using `executable-find'. If
`matlab-shell-command' is
MATLAB installed command found using
`matlab--default-matlab-exe'.
-If NO-ERROR is t, and matlab command is not found, nil is return,
-otherwise an error is signaled."
+If NO-ERROR is non-nil, and matlab command is not found, nil is
+returned, otherwise an error is signaled."
(condition-case err
(let (abs-matlab-exe)
(cond
diff --git a/tests/README-TEST-MATLAB-TREE-SITTER.org
b/tests/README-TEST-MATLAB-TREE-SITTER.org
new file mode 100644
index 0000000000..9a0bf65ba8
--- /dev/null
+++ b/tests/README-TEST-MATLAB-TREE-SITTER.org
@@ -0,0 +1,208 @@
+# File: tests/README-TEST-MATLAB-TREE-SITTER.org
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+#+startup: showall
+#+options: toc:nil
+
+#+title: MATLAB tree-sitter only testing
+#+author: John Ciolfi
+#+date: Nov-24-2025
+
+Emacs leverages https://github.com/acristoffers/tree-sitter-matlab for
matlab-ts-mode and thus has
+tests for it. The tests below /only/ exercise the matlab tree-sitter grammar
shared library and not the
+use of the shared library by matlab-ts-mode. The tests use Emacs as a "test
driver".
+
+* Setup
+
+To run the tests,
+
+1. Install Emacs 30 or later and validate emacs is on your system path and
runs the desired version:
+
+ #+begin_src bash
+ emacs --version
+ #+end_src
+
+ Often package managers will contain Emacs 30.
+
+ - On Debian 12, you can install Emacs 30 using:
+
+ #+begin_src bash
+ apt update
+ apt -t bookworm-backports install emacs
+ #+end_src
+
+ - On Mac, you can install Emacs 30 from https://emacsformacosx.com/
+
+2. Install Emacs-MATLAB-Mode
+
+ #+begin_src bash
+ cd /YOUR/WORK/DIRECTORY # Any directory is fine
+
+ git clone https://github.com/mathworks/Emacs-MATLAB-Mode.git
+ cd Emacs-MATLAB-Mode
+ make lisp
+ #+end_src
+
+3. Build the matlab tree-sitter grammar
+
+ The grammar shared library must be named =libtree-sitter-matlab.SLIB_EXT=
where SLIB_EXT is =so=
+ on Linux, =dylib= on Mac, or =dll= on Windows.
+
+ By default, the shared library should be placed in
+ =~/.emacs.d/tree-sitter/libtree-sitter-matlab.SLIB_EXT=. You can place it
in another location
+ and tell Emacs where it is using the =-libtree-sitter-matlab= switch in the
tests below.
+
+ ABI 14 is required by Emacs 30. On Debian 12, you can build using:
+
+ #+begin_src bash
+ cd /YOUR/WORK/DIRECTORY
+ if [ -e tree-sitter-matlab ]; then
+ rm -rf tree-sitter-matlab
+ fi
+ git clone https://github.com/acristoffers/tree-sitter-matlab
+ cd tree-sitter-matlab
+
+ git checkout abi/14
+
+ cd tree-sitter-matlab/src
+ cc -fPIC -O -c -I. parser.c
+ cc -fPIC -O -c -I. scanner.c
+ cc -fPIC -shared parser.o scanner.o -o
~/.emacs.d/tree-sitter/libtree-sitter-matlab.so
+ #+end_src
+
+* Test: sweep-test-matlab-ts-grammar.sh
+
+Check matlab tree-sitter parse by /sweeping/ over the =*.m= files in a
directory tree.
+
+This validates that if MATLAB tree-sitter parse has ERROR nodes that the
MATLAB codeIssues command,
+https://www.mathworks.com/help/matlab/ref/codeissues.html says the file has
syntax issues (issue
+severity of error). Likewise, if MATLAB tree-sitter parse says no syntax
errors, this test confirms
+that the MATLAB codeIssues command reports the same.
+
+To use sweep-test-matlab-ts-grammar.sh
+
+1. Run sweep test
+
+ #+begin_src bash
+ cd /PATH/TO/DIRECTORY/CONTAINING/MFILES
+
/YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/sweep-test-matlab-ts-grammar.sh
+ #+end_src
+
+ If your grammar shared library is not located at
+ =~/.emacs.d/tree-sitter/libtree-sitter-matlab.SLIB_EXT=, add option
+
+ : -libtree-sitter-matlab /PATH/TO/libtree-sitter-matlab.SLIB_EXT
+
+2. Results
+
+ - *sweep-test-matlab-ts-grammar.log*
+
+ This lists the files parsed and whether or not they have syntax errors.
+
+ - *sweep-test-matlab-ts-grammar.result.txt*
+
+ This is created if the matlab command is available (=which matlab=). The
codeIssues() MATLAB
+ command is used to compare MATLAB parse v.s. the matlab tree-sitter parse
and has sections:
+
+ : Files-with-parse-error-nodes-but-pass-syntax-checker-fun:
+ : <files with tree-sitter error nodes>
+ :
+ : Files-that-parsed-successfully-but-failed-syntax-checker-fun:
+ : <files without tree-sitter error nodes>
+ :
+ : Total-consistently-parsed-files: M of N
+
+* Test: test-matlab-ts-mode-parser.sh
+
+This test loops over all =*.m= files under the
[[file:test-matlab-ts-mode-parser-files][./test-matlab-ts-mode-parser-files]]
directory tree and
+compares the matlab tree-sitter annotated parse tree against =*_expected.txt=.
In this directory, we
+have paired files, =NAME.m= and =NAME_expected.txt= where =NAME.m= contains
MATLAB code and
+=NAME_expected.txt= is the expected annotated parse tree.
+
+1. Run parser test
+
+ #+begin_src bash
+ cd /PATH/TO/DIRECTORY/CONTAINING/MFILES
+ /YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser.sh
+ #+end_src
+
+ If your grammar shared library is not located at
+ =~/.emacs.d/tree-sitter/libtree-sitter-matlab.SLIB_EXT=, add option
+
+ : -libtree-sitter-matlab /PATH/TO/libtree-sitter-matlab.SLIB_EXT
+
+2. Results
+
+ If the annotated parse tree for =NAME.m= doesn't match NAME_expected.txt,
you will see:
+
+ #+begin_example
+ (("Test:
/YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser-files/parser_cell.m"
+ "Got:
/YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser-files/parser_cell_expected.txt~"
+ "Expected:
/YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser-files/parser_cell_expected.txt"))
+ #+end_example
+
+ and you can compare the 'Got' file with the 'Expected' file.
+
+ Difference in the annotated parse tree could be okay, in which case we'll
need to update the
+ =*_expected.txt= baselines.
+
+** test-matlab-ts-mode-parser annotated Parse Tree, NAME_expected.txt
+
+The annotate parse tree is a concrete syntax tree for the current buffer. The
tree contains named
+and anonymous nodes. Given:
+
+ #+begin_src matlab
+ a = 1;
+ b = 2;
+ c = a + b;
+ #+end_src
+
+the annotated parse tree is:
+
+#+begin_example
+ (source_file<1,28>
+ (assignment<1,6>
+ left: (identifier[1,2]@{a}@)
+ =[3,4]
+ right: (number[5,6]@{1}@))
+ ;[6,7]
+ (assignment<8,13>
+ left: (identifier[8,9]@{b}@) =[10,11]
+ right: (number[12,13]@{2}@))
+ ;[13,14]
+ (assignment<15,24> left: (identifier[15,16]@{c}@) =[17,18]
+ right: (binary_operator<19,24>
+ left: (identifier[19,20]@{a}@) +[21,22]
+ right: (identifier[23,24]@{b}@)))
+ ;[24,25] \\n[25,28])
+#+end_example
+
+which contains:
+
+- leaf named nodes: "identifier" "number"
+
+- leaf anonymous nodes (node has same name as code text): "=" "+" "\n"
+
+- non-leaf nodes: "source_file" "assignment" "binary_operator"
+
+Notice that near each node we have a range of form =<START,END>= or
=[START,END]= where the node
+text starts at START point and ends one before END point. The point is the
logical character
+position in the buffer. The length of text for the node is =END-START=. For
named nodes, the first
+50 characters of the node text is shown in =@{NODE_TEXT}@=.
+
+** M-x t-utils-view-parse-tree
+
+You can view the annotated parse tree of a =*.m= file using:
+
+1. Run Emacs
+ : emacs
+2. Load t-utils.el
+ : M-x load-file RET /YOUR/WORK/DIRECTORY/Emacs-MATLAB-Mode/tests/t-utils.el
RET
+3. View a =*.m= file
+ : C-x C-f NAME.m
+4. View the annotated parse tree
+ : M-x t-utils-view-parse-tree
+ You can click with mouse or type RET on the =[START,END]= ranges to
highlight the text in
+ =NAME.m=.
+
+# LocalWords: showall backports libtree SLIB dylib ABI abi MFILES utils
diff --git a/tests/sweep-test-matlab-ts-grammar.el
b/tests/sweep-test-matlab-ts-grammar.el
index 0340041895..7ede6965a2 100644
--- a/tests/sweep-test-matlab-ts-grammar.el
+++ b/tests/sweep-test-matlab-ts-grammar.el
@@ -28,15 +28,11 @@
(require 'matlab-ts-mode)
(require 'matlab--access)
+(defun sweep-test-matlab-ts-grammar--run-syntax-check (matlab-exe m-files)
+ "Using MATLAB-EXE check M-FILES using MATLAB checkIssue.
+See `sweep-test-matlab-ts-grammar--syntax-checker' for return."
-(defun sweep-test-matlab-ts-grammar--syntax-checker (m-files)
- "Syntax check each *.m file in M-FILES using MATLAB checkIssue.
-
-Returns hash table where the keys are the m-files and each key
-value is either \"no-syntax-errors\" or \"has-syntax-errors\"."
- (let* ((matlab-exe (or (matlab--get-abs-matlab-exe)
- (error "No matlab found (to fix put matlab on your
PATH)")))
- (tmp-check-file (make-temp-file "sweep_test_matlab_ts_grammar" nil
".m"))
+ (let* ((tmp-check-file (make-temp-file "sweep_test_matlab_ts_grammar" nil
".m"))
(check-fun (file-name-sans-extension (file-name-nondirectory
tmp-check-file)))
(tmp-check-file-dir (file-name-directory tmp-check-file))
(result-ht (make-hash-table :test 'equal)))
@@ -194,6 +190,19 @@ end
(delete-file tmp-check-file)
result-ht))
+(defun sweep-test-matlab-ts-grammar--syntax-checker (m-files)
+ "Syntax check each *.m file in M-FILES using MATLAB checkIssue.
+
+Returns hash table where the keys are the m-files and each key
+value is either \"no-syntax-errors\" or \"has-syntax-errors\"."
+ (let ((matlab-exe (matlab--get-abs-matlab-exe 'no-error)))
+ (if matlab-exe
+ (sweep-test-matlab-ts-grammar--run-syntax-check matlab-exe m-files)
+ (message (concat "Unable to use MATLAB codeIssues() command to compare
against matlab "
+ "tree-sitter because matlab is not found.\n"
+ "Examine the *.log file for results.\n"))
+ nil)))
+
(defun sweep-test-matlab-ts-grammar (&optional directory log-file)
"Check matlab tree-sitter parse of all *.m files under DIRECTORY.
DIRECTORY defaults to the current directory.
diff --git a/tests/sweep-test-matlab-ts-grammar.sh
b/tests/sweep-test-matlab-ts-grammar.sh
index d600c4f768..b5b9f65b4f 100755
--- a/tests/sweep-test-matlab-ts-grammar.sh
+++ b/tests/sweep-test-matlab-ts-grammar.sh
@@ -1,38 +1,23 @@
#!/usr/bin/bash
# File: Emacs-MATLAB-Mode/tests/sweep-test-matlab-ts-grammar.sh
# Abstract:
-# cd /your/work/directory
#
-# git clone https://github.com/mathworks/Emacs-MATLAB-Mode.git
-# cd Emacs-MATLAB-Mode
-# make lisp
-#
-# cd /path/to/directory/containing/mFiles
-# /path/to/Emacs-MATLAB-Mode/tests/sweep-test-matlab-ts-grammar.sh
-#
-# Results are saved in sweep-test-matlab-ts-grammar.result.txt
-# Files-with-parse-error-nodes-but-pass-syntax-checker-fun:
-# <files with tree-sitter error nodes>
-#
-# Files-that-parsed-succesfully-but-failed-syntax-checker-fun:
-# <files without tree-sitter error nodes>
-#
-# Total-consistently-parsed-files: M of N
+# See ./README-TEST-MATLAB-TREE-SITTER.org for usage.
#
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
-if ! command -v matlab > /dev/null 2>&1
-then
- echo "matlab is not found (which matlab)"
- exit 1
-fi
+. "$SCRIPT_DIR/test-tree-sitter-utils.sh"
-EmacsMATLABModeDir=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null
&& cd .. && pwd)
+set -x
-emacs --batch \
- -q \
+emacs --batch -q "${tsExtraLoadPath[@]}" \
-L "$EmacsMATLABModeDir" \
-l "$EmacsMATLABModeDir/matlab-autoload.el" \
-L "$EmacsMATLABModeDir/tests" \
-l "$EmacsMATLABModeDir/tests/t-utils.el" \
-l "$EmacsMATLABModeDir/tests/sweep-test-matlab-ts-grammar.el" \
-f sweep-test-matlab-ts-grammar
+
+# LocalWords: usr MFILES dylib slib uname elif esac libtree realpath fn setq
treesit dev utils
diff --git a/tests/t-utils.el b/tests/t-utils.el
index 818929b15f..284c99631b 100644
--- a/tests/t-utils.el
+++ b/tests/t-utils.el
@@ -2517,14 +2517,25 @@ each element is a cons pair (NAME . NODE)."
result-list)))
(reverse result-list)))
-(defun t-utils-sweep-test-ts-grammar (test-name
- directory
- lang-file-regexp
- major-mode-fun
- syntax-checker-fun
- &optional error-node-type
- log-file
- result-file)
+(defun t-utils--err-locs-str (err-locs lang-file)
+ "Return a string representing ERR-LOCS in LANG-FILE."
+ (let ((str ""))
+ (dolist (line err-locs)
+ (setq str (concat
+ str
+ (when (string-match "at line \\([0-9]+:[0-9]+\\)" line)
+ (concat lang-file ":" (match-string 1 line) ": error: "))
+ line)))
+ str))
+
+(cl-defun t-utils-sweep-test-ts-grammar (test-name
+ directory
+ lang-file-regexp
+ major-mode-fun
+ syntax-checker-fun
+ &optional error-node-type
+ log-file
+ result-file)
"Sweep test a tree-sitter grammar shared library looking for parse issues.
File base names matching LANG-FILE-REGEXP under DIRECTORY
@@ -2605,8 +2616,10 @@ otherwise the result is displayed on stdout."
error-nodes))
(cons "no-syntax-errors" nil))))
(puthash lang-file syntax-status-pair ts-parse-result-ht)
- (t-utils--log log-file (format "ts-parse: %s > %S\n"
- lang-file
syntax-status-pair)))))))
+ (t-utils--log log-file (format "ts-parse: %s > %s\n%s"
+ lang-file (car syntax-status-pair)
+ (t-utils--err-locs-str (cdr
syntax-status-pair)
+
lang-file))))))))
(when (= (length lang-files-to-check) 0)
(user-error "No files to check (all skipped)\n"))
@@ -2619,6 +2632,10 @@ otherwise the result is displayed on stdout."
(files-with-bad-ts-success-parse "")
(n-consistent-files 0))
+ ;; nil returned by syntax-checker-fun means it couldn't check
+ (when (not syntax-check-result-ht)
+ (cl-return-from t-utils-sweep-test-ts-grammar))
+
(t-utils--log log-file (format "Examining %S result\n"
syntax-checker-fun))
(dolist (lang-file lang-files-to-check)
diff --git a/tests/test-matlab-ts-mode-parser.el
b/tests/test-matlab-ts-mode-parser.el
index 9c66db7e1f..21351f104f 100644
--- a/tests/test-matlab-ts-mode-parser.el
+++ b/tests/test-matlab-ts-mode-parser.el
@@ -127,9 +127,6 @@ M-FILE is not in test-matlab-ts-mode-parser-files"
(list update m-file test-m-file)))
-;; TODO update the files and add ert test to validate
(test-matlab-ts-mode-parser--all-files)
-;; returns ""
-
(defun test-matlab-ts-mode-parser--all-files (&optional update-if-needed)
"Verify we use ./test-matlab-ts-mode-*/*.m as baselines.
We use all ./test-matlab-ts-mode-*/*.m files as test points.
@@ -189,6 +186,10 @@ you that updates are needed."
"to update them")
n-updates))))))
+(defun batch-test-matlab-ts-mode-parser ()
+ "Run test-matlab-ts-mode-parser when in batch mode."
+ (ert-run-tests-batch-and-exit "\\`test-matlab-ts-mode-parser\\'"))
+
(provide 'test-matlab-ts-mode-parser)
;;; test-matlab-ts-mode-parser.el ends here
diff --git a/tests/test-matlab-ts-mode-parser.sh
b/tests/test-matlab-ts-mode-parser.sh
new file mode 100755
index 0000000000..ce9b08adaf
--- /dev/null
+++ b/tests/test-matlab-ts-mode-parser.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/bash
+# File: Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser.sh
+# Abstract:
+#
+# See ./README-TEST-MATLAB-TREE-SITTER.org for usage.
+#
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
+
+. "$SCRIPT_DIR/test-tree-sitter-utils.sh"
+
+set -x
+
+emacs --batch -q "${tsExtraLoadPath[@]}" \
+ -L "$EmacsMATLABModeDir" \
+ -l "$EmacsMATLABModeDir/matlab-autoload.el" \
+ -L "$EmacsMATLABModeDir/tests" \
+ -l "$EmacsMATLABModeDir/tests/t-utils.el" \
+ -l "$EmacsMATLABModeDir/tests/test-matlab-ts-mode-parser.el" \
+ -f batch-test-matlab-ts-mode-parser
diff --git a/tests/test-tree-sitter-utils.sh b/tests/test-tree-sitter-utils.sh
new file mode 100644
index 0000000000..846e77cde3
--- /dev/null
+++ b/tests/test-tree-sitter-utils.sh
@@ -0,0 +1,93 @@
+#!/bin/bash
+# File: Emacs-MATLAB-Mode/tests/test-tree-sitter-utils.sh
+# Abstract:
+# Argument parsing for matlab tree-sitter testing which run Emacs and accept
argument:
+#
+# testName.sh -libtree-sitter-matlab /PATH/TO/libtree-sitter-matlab.SLIB_EXT
+#
+# Copyright (C) 2025 Free Software Foundation, Inc.
+
+#----------------------------------------------#
+# Get shared library extension: so, dylib, dll #
+#----------------------------------------------#
+if [ "$OS" = "Windows_NT" ]; then
+ slibExt="dll"
+else
+ if [ -x /bin/uname ]; then
+ uname="/bin/uname";
+ elif [ -x /usr/bin/uname ]; then
+ uname="/usr/bin/uname";
+ else
+ echo "No uname found"
+ fi
+
+ case "$($uname)" in
+ Linux)
+ slibExt=so
+ ;;
+ Darwin)
+ slibExt=dylib
+ ;;
+ *)
+ echo "$(uname) is not supported"
+ esac
+fi
+
+#-------------#
+# Check usage #
+#-------------#
+
+libTreeSitterMatlab=""
+
+while [[ $# -gt 0 ]]
+do
+ case $1 in
+
+ -libtree-sitter-matlab)
+ shift
+ if [[ $# -ne 1 ]]; then
+ echo "Shared library not provided: \
+-libtree-sitter-matlab /PATH/TO/libtree-sitter-matlab.SLIB_EXT"
+ exit 1
+ fi
+ libTreeSitterMatlab="$1"
+ shift
+ if [ ! -f "$libTreeSitterMatlab" ]; then
+ echo "$libTreeSitterMatlab" does not exist
+ exit 1
+ fi
+ libTreeSitterMatlab=$(realpath "$libTreeSitterMatlab")
+ fn=$(basename "$libTreeSitterMatlab")
+ if [[ ! "$fn" =~ ^libtree-sitter-matlab\.$slibExt ]]; then
+ echo "-libtree-sitter-matlab basename must be
libtree-sitter-matlab.$slibExt"
+ exit 1
+ fi
+ ;;
+ -help)
+ echo "$0 [-libtree-sitter-matlab
/PATH/TO/libtree-sitter-matlab.SLIB_EXT]"
+ exit 0
+ ;;
+ *)
+ echo "Invalid option: $1"
+ exit 0
+ ;;
+ esac
+done
+
+#-------------------------------------------------------------------------#
+# -libtree-sitter-matlab /PATH/TO/libtree-sitter-matlab.SLIB_EXT handling #
+#-------------------------------------------------------------------------#
+declare -a tsExtraLoadPath
+if [ "$libTreeSitterMatlab" != "" ]; then
+ p=$(dirname "$libTreeSitterMatlab")
+ tsExtraLoadPath=("--eval"
+ "(setq treesit-extra-load-path (list \"$p\"))")
+else
+ tsExtraLoadPath=()
+fi
+export tsExtraLoadPath
+
+EmacsMATLABModeDir=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null
&& cd .. && pwd)
+export EmacsMATLABModeDir
+
+# LocalWords: libtree dylib slib uname elif esac realpath fn setq treesit