branch: externals/matlab-mode
commit 98867020ac102a13949a3507aec0d6381119bc5b
Author: John Ciolfi <[email protected]>
Commit: John Ciolfi <[email protected]>

    tests: enable reuse of some tests for matlab tree-sitter
---
 matlab--access.el                        |   4 +-
 tests/README-TEST-MATLAB-TREE-SITTER.org | 218 +++++++++++++++++++++++++++++++
 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      |  19 +++
 tests/test-tree-sitter-utils.sh          |  92 +++++++++++++
 8 files changed, 387 insertions(+), 48 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..dbe2c8268e
--- /dev/null
+++ b/tests/README-TEST-MATLAB-TREE-SITTER.org
@@ -0,0 +1,218 @@
+# File: tests/README-TEST-MATLAB-TREE-SITTER.org
+#
+
+#+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 you 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 using:
+     
+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 in the next step.
+
+   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 all *.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 matlab is available. If matlab is available, the 
codeIssues()
+     matlab command is used to compare matlab parse v.s. the matlab 
tree-sitter parse
+     and has contains 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.
+
+** 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
+
+   #+begin_src bash
+     emacs
+   #+end_src
+   
+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..6692c5d05f 100755
--- a/tests/sweep-test-matlab-ts-grammar.sh
+++ b/tests/sweep-test-matlab-ts-grammar.sh
@@ -1,38 +1,21 @@
 #!/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.
+
+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..c12de81611
--- /dev/null
+++ b/tests/test-matlab-ts-mode-parser.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/bash
+# File: Emacs-MATLAB-Mode/tests/test-matlab-ts-mode-parser.sh
+# Abstract:
+#
+#   See ./README-TEST-MATLAB-TREE-SITTER.org for usage.
+
+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..428f331fe3
--- /dev/null
+++ b/tests/test-tree-sitter-utils.sh
@@ -0,0 +1,92 @@
+#!/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
+#
+
+#----------------------------------------------#
+# 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

Reply via email to