* [PATCH] Add tests for ob-haskell (GHCi) @ 2023-03-19 9:12 Bruno Barbier 2023-03-19 10:20 ` Ihor Radchenko 2023-03-23 10:35 ` Ihor Radchenko 0 siblings, 2 replies; 26+ messages in thread From: Bruno Barbier @ 2023-03-19 9:12 UTC (permalink / raw) To: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 1059 bytes --] Hi all, I've collected some tests about ob-haskell, using GHCi. This patch is not ready for final review. It's a preliminary version. These tests are about GHCi, and only about GHCi (that's why I named the file 'test-ob-haskell-ghci.el'). Some tests are revealing bugs; they are flagged with: :expected-result :failed All the tests will randomly fail; that is an ob-haskell bug (see the command 'test-ob-haskell-ghci.el' to make them fail). Some tests might be questionable, revealing more design choices and/or backend capabilities than the expected behavior. Solving the random failure bug should be a priority, in my opinion, as it makes the testing process unreliable. Adding support for sessions would also be a good thing, so that one test doesn't contaminate others. FTR, here are the tools that I have used: | emacs-version | 29.0.60 | | org-version | main@4cad6c8ea (Mar 16 2023) | | haskell-mode | master@20d4e23 (Mar 4 2023) | | ghci | 9.0.2 | Bruno [-- Attachment #2: Add tests for haskell (GHCi) --] [-- Type: text/x-patch, Size: 14254 bytes --] From c085fac2fcb429f7e643df8e4fff3a4ae1665d07 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 428 +++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..4b1e4669b --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,428 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. + +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html + +;;;; FIXME: Random failures +;; +;; To increase the chances of failure when running tests, you can use this command line: +;; +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED +;; + +;;;; Status +;; +;; All the tests should succeed (except for random failures); those +;; flagged with ":expected-result :failed" are known +;; limitations/bugs. Tested with (2023-03-18): +;; +;; | emacs-version | 29.0.60 | +;; | org-version | main@4cad6c8ea (Mar 16 2023) | +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | +;; | ghci | 9.0.2 | + + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci--with-global-session-worker (todo) + "See `test-ob-haskell-ghci--with-global-session-worker'." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (unwind-protect (funcall todo) + ;; Kill the "*haskell*" buffer to not pollute other tests. + (when-let ((hb (get-buffer "*haskell*"))) + (with-current-buffer hb + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer hb)))))) + +(defmacro test-ob-haskell-ghci-with-global-session (&rest body) + "Eval BODY in a new session, then destroy the session. +The library ob-haskell doesn't implement session yet. It will +always use a buffer named \"*haskell*\". We kill that buffer +after the source block execution. To be safe, we fail if such a +buffer already exists." + `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, don't control +which session is used (i.e. don't call +`test-ob-haskell-ghci--with-global-session-worker')." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-with-global-session (funcall todo))))) + + +;;; Tests + + +;;;; Temporary tests +;; + +(ert-deftest ob-haskell/check-version () + "Check GHCi version. +Only tested with GHCi 9.0.2." + (should (equal '("linux" "ghc" 9) (test-ob-haskell-ghci "" " +import System.Info +import Data.Version +(os, compilerName, (versionBranch fullCompilerVersion)!!0) +")))) + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + + +;;;; Not define errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + (let ((r (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + (should (and r (string-match "Variable not in scope" r))))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.1 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-19 9:12 [PATCH] Add tests for ob-haskell (GHCi) Bruno Barbier @ 2023-03-19 10:20 ` Ihor Radchenko 2023-03-19 10:28 ` Ihor Radchenko 2023-03-19 10:32 ` Bruno Barbier 2023-03-23 10:35 ` Ihor Radchenko 1 sibling, 2 replies; 26+ messages in thread From: Ihor Radchenko @ 2023-03-19 10:20 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: > All the tests will randomly fail; that is an ob-haskell bug (see > the command 'test-ob-haskell-ghci.el' to make them fail). First of all, thanks for the patch! Tests are especially welcome. From a first look, random failures appear to be related to session initialization. It appears that ob-haskell is re-using sessions even if :session is nil (default). And multiple session not allowed?? (I am looking at `org-babel-haskell-initiate-session', which ignores session parameter completely). -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-19 10:20 ` Ihor Radchenko @ 2023-03-19 10:28 ` Ihor Radchenko 2023-03-19 10:32 ` Bruno Barbier 1 sibling, 0 replies; 26+ messages in thread From: Ihor Radchenko @ 2023-03-19 10:28 UTC (permalink / raw) To: Bruno Barbier, Lawrence Bottorff; +Cc: emacs-orgmode Ihor Radchenko <yantar92@posteo.net> writes: > From a first look, random failures appear to be related to session > initialization. It appears that ob-haskell is re-using sessions even > if :session is nil (default). And multiple session not allowed?? (I am > looking at `org-babel-haskell-initiate-session', which ignores session > parameter completely). Lawrence, may you please take a look at this thread? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-19 10:20 ` Ihor Radchenko 2023-03-19 10:28 ` Ihor Radchenko @ 2023-03-19 10:32 ` Bruno Barbier 2023-03-22 10:16 ` Ihor Radchenko 1 sibling, 1 reply; 26+ messages in thread From: Bruno Barbier @ 2023-03-19 10:32 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode Hi Ihor, Ihor Radchenko <yantar92@posteo.net> writes: > From a first look, random failures appear to be related to session > initialization. My function 'test-ob-haskell-ghci' should protect against that; it ensures the "*haskell*" buffer is always new. From what I understand, this is an input buffering problem: inputs are not full lines. > It appears that ob-haskell is re-using sessions even > if :session is nil (default). And multiple session not allowed?? (I am > looking at `org-babel-haskell-initiate-session', which ignores session > parameter completely). From what I understood, the buffer "*haskell*" is coming from haskell-mode. And, to make it work might require some changes there too. Thanks for having a look, Let me know, Bruno > > -- > Ihor Radchenko // yantar92, > Org mode contributor, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-19 10:32 ` Bruno Barbier @ 2023-03-22 10:16 ` Ihor Radchenko 2023-03-24 10:36 ` Ihor Radchenko 0 siblings, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-03-22 10:16 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 478 bytes --] Bruno Barbier <brubar.cs@gmail.com> writes: > Ihor Radchenko <yantar92@posteo.net> writes: > >> From a first look, random failures appear to be related to session >> initialization. > > My function 'test-ob-haskell-ghci' should protect against that; it > ensures the "*haskell*" buffer is always new. From what I understand, > this is an input buffering problem: inputs are not full lines. I see. We can try the attached patch. Yet another edge case in comint, it appears. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: 0001-org-babel-comint-with-output-Handle-agglomerated-pro.patch --] [-- Type: text/x-patch, Size: 2215 bytes --] From 2bdcec1e1f9a3ce0314d182a4c50887e8ec80a17 Mon Sep 17 00:00:00 2001 Message-Id: <2bdcec1e1f9a3ce0314d182a4c50887e8ec80a17.1679480064.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Wed, 22 Mar 2023 11:12:30 +0100 Subject: [PATCH] org-babel-comint-with-output: Handle agglomerated prompts better * lisp/ob-comint.el (org-babel-comint-with-output): Consider that comint can sometimes agglomerate multiple prompts together even within a single output increment as passed to `comint-output-filter-functions'. Example in GHC comint buffer: GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help ghci> ghci> :{ main :: IO () main = putStrLn "Hello World!" :} main "org-babel-haskell-eoe" ghci| ghci| ghci| ghci> ghci> Hello World! ghci> "org-babel-haskell-eoe" ghci> --- lisp/ob-comint.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lisp/ob-comint.el b/lisp/ob-comint.el index 54bf5127e..e167d911b 100644 --- a/lisp/ob-comint.el +++ b/lisp/ob-comint.el @@ -91,7 +91,16 @@ (defmacro org-babel-comint-with-output (meta &rest body) ;; trailing newline. Use more reliable ;; match to split the output later. (replace-regexp-in-string - comint-prompt-regexp + ;; Sometimes, we get multiple agglomerated + ;; prompts together in a single output: + ;; "prompt prompt prompt output" + ;; Remove them progressively, so that + ;; possible "^" in the prompt regexp gets to + ;; work as we remove the heading prompt + ;; instance. + (if (string-prefix-p "^" comint-prompt-regexp) + (format "^\\(%s\\)+" (substring comint-prompt-regexp 1)) + comint-prompt-regexp) ,org-babel-comint-prompt-separator text)))) comint-output-filter-functions)) -- 2.39.1 [-- Attachment #3: Type: text/plain, Size: 722 bytes --] >> It appears that ob-haskell is re-using sessions even >> if :session is nil (default). And multiple session not allowed?? (I am >> looking at `org-babel-haskell-initiate-session', which ignores session >> parameter completely). > > From what I understood, the buffer "*haskell*" is coming from > haskell-mode. And, to make it work might require some changes there too. From my reading of haskell-mode, it seems to be safe if we simply rename Haskell process buffer upon session initialization. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-22 10:16 ` Ihor Radchenko @ 2023-03-24 10:36 ` Ihor Radchenko 2023-03-25 10:01 ` Bruno Barbier 0 siblings, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-03-24 10:36 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 1579 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > We can try the attached patch. Yet another edge case in comint, it > appears. I investigated further and now applied a set of patches that improves prompt filtering in org-comint. https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=a8a516ba330fe8c435334030ffbe371b8c80c877 https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=866ed1a3c5c37cad243085f9a8fa904970e4d614 Further, I tried to incorporate Pareto's suggestion about secondary prompt and cleaned up the original GHCi tests. With the attached set of patches (first one is Bruno's original patch), I get all the tests reliably passing, except the tests left as :expected-result failed. The remaining tests fall into two categories: 1. Tests trying to test :results value vs. :results output in sessions. 2. Tests trying to test for multiple sessions not interfering each other. The first category of tests fails because `org-babel-interpret-haskell' simply cannot distinguish between :results value and :results output. Someone familiar with GHC should handle this problem. For example, by wrapping :results output code in such a way that output is discarded in the GHCi comint buffer. The second category of tests fails because haskell-mode uses a fixed "*haskell*" comint buffer name. As I suggested in another message, a viable approach could be renaming the original "*haskell*" buffer to something else in `org-babel-haskell-initiate-session'. Again, someone more familiar with haskell-mode should judge if such option is truly viable. [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 14349 bytes --] From 1273b5b415fa420e718645452bc75bc7a9407af5 Mon Sep 17 00:00:00 2001 Message-Id: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 1/4] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 428 +++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..4b1e4669b --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,428 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. + +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html + +;;;; FIXME: Random failures +;; +;; To increase the chances of failure when running tests, you can use this command line: +;; +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED +;; + +;;;; Status +;; +;; All the tests should succeed (except for random failures); those +;; flagged with ":expected-result :failed" are known +;; limitations/bugs. Tested with (2023-03-18): +;; +;; | emacs-version | 29.0.60 | +;; | org-version | main@4cad6c8ea (Mar 16 2023) | +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | +;; | ghci | 9.0.2 | + + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci--with-global-session-worker (todo) + "See `test-ob-haskell-ghci--with-global-session-worker'." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (unwind-protect (funcall todo) + ;; Kill the "*haskell*" buffer to not pollute other tests. + (when-let ((hb (get-buffer "*haskell*"))) + (with-current-buffer hb + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer hb)))))) + +(defmacro test-ob-haskell-ghci-with-global-session (&rest body) + "Eval BODY in a new session, then destroy the session. +The library ob-haskell doesn't implement session yet. It will +always use a buffer named \"*haskell*\". We kill that buffer +after the source block execution. To be safe, we fail if such a +buffer already exists." + `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, don't control +which session is used (i.e. don't call +`test-ob-haskell-ghci--with-global-session-worker')." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-with-global-session (funcall todo))))) + + +;;; Tests + + +;;;; Temporary tests +;; + +(ert-deftest ob-haskell/check-version () + "Check GHCi version. +Only tested with GHCi 9.0.2." + (should (equal '("linux" "ghc" 9) (test-ob-haskell-ghci "" " +import System.Info +import Data.Version +(os, compilerName, (versionBranch fullCompilerVersion)!!0) +")))) + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + + +;;;; Not define errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + (let ((r (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + (should (and r (string-match "Variable not in scope" r))))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1563 bytes --] From 0112cb1b2531a8defb51234844badc74dad07e5e Mon Sep 17 00:00:00 2001 Message-Id: <0112cb1b2531a8defb51234844badc74dad07e5e.1679653723.git.yantar92@posteo.net> In-Reply-To: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> References: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 2/4] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 2b1441c2a..f4197654c 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,14 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.39.1 [-- Attachment #4: 0003-testing-lisp-test-ob-haskell-ghci.el-Fix-some-tests.patch --] [-- Type: text/x-patch, Size: 1782 bytes --] From 99ffca55a5363a14f4f7df96348e2fdeeab68729 Mon Sep 17 00:00:00 2001 Message-Id: <99ffca55a5363a14f4f7df96348e2fdeeab68729.1679653723.git.yantar92@posteo.net> In-Reply-To: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> References: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:25:19 +0100 Subject: [PATCH 3/4] * testing/lisp/test-ob-haskell-ghci.el: Fix some tests (ob-haskell/2D-lists-multilines): (ob-haskell/ghci-info): Fix incorrect test assertions. --- testing/lisp/test-ob-haskell-ghci.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 4b1e4669b..99b2e1973 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -275,8 +275,7 @@ (ert-deftest ob-haskell/2D-lists () (ert-deftest ob-haskell/2D-lists-multilines () "Evaluation of nested lists into a table, as multilines." - :expected-result :failed - (should (equal '((1 2 3) (4 5 6)) + (should (equal '((1 2 3) (4 5 6) (7 8 9)) (test-ob-haskell-ghci "" " :{ [ [1..3] @@ -419,8 +418,9 @@ (ert-deftest ob-haskell/ghci-type () (ert-deftest ob-haskell/ghci-info () "The ghci meta command ':info' ." - (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" - (test-ob-haskell-ghci ":results output" ":info repeat")))) + (should (string-match-p + "repeat :: a -> \\[a\\][ \t]+-- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) (provide 'test-ob-haskell-ghci) -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2395 bytes --] From eddff6135407975316dfecc8e10fb3e2be47f9ad Mon Sep 17 00:00:00 2001 Message-Id: <eddff6135407975316dfecc8e10fb3e2be47f9ad.1679653723.git.yantar92@posteo.net> In-Reply-To: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> References: <1273b5b415fa420e718645452bc75bc7a9407af5.1679653723.git.yantar92@posteo.net> From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 4/4] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 99b2e1973..88f628b72 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -131,7 +131,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -201,7 +200,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -213,7 +211,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -248,7 +245,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -334,7 +330,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.39.1 [-- Attachment #6: Type: text/plain, Size: 224 bytes --] -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-24 10:36 ` Ihor Radchenko @ 2023-03-25 10:01 ` Bruno Barbier 2023-03-26 9:09 ` Ihor Radchenko 0 siblings, 1 reply; 26+ messages in thread From: Bruno Barbier @ 2023-03-25 10:01 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 1242 bytes --] Ihor Radchenko <yantar92@posteo.net> writes: > I investigated further and now applied a set of patches that improves > prompt filtering in org-comint. > .. Thanks Ihor. I don't get random failures anymore. And thanks for fixing the tests that were incorrect. Thanks Pareto for mentioning the possibiliy to customize GHCi prompts. > The remaining tests fall into two categories: > 1. Tests trying to test :results value vs. :results output in sessions. > 2. Tests trying to test for multiple sessions not interfering each > other. About :results output vs :results value, I decided to use the "it" variable, i.e. to ask GHCi what was the last value (see [1]). About sessions, I decided to rename the buffer as you suggested. That's a partial workaround that should work well enough, until haskell-mode provides a way to choose the buffer name. I've updated the tests and the expected results. Note that I've changed the tests about errors; I'm now expecting ob-haskell to raise errors. I'm not sure what we should expect to be consistent with other org babel backends. I also did some modifications in my initial patch in-place. Bruno [1]:https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html#the-it-variable [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 15043 bytes --] From 46e8fa78574908a15fe6eb82a2cca5d2f537c78e Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 1/7] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 453 +++++++++++++++++++++++++++ 1 file changed, 453 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..aba94d73f --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,453 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. + +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html +;;;; FIXME: Random failures +;; +;; To increase the chances of failure when running tests, you can use this command line: +;; +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED +;; + +;;;; Status +;; +;; All the tests should succeed (except for random failures); those +;; flagged with ":expected-result :failed" are known +;; limitations/bugs. Tested with (2023-03-18): +;; +;; | emacs-version | 29.0.60 | +;; | org-version | main@4cad6c8ea (Mar 16 2023) | +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | +;; | ghci | 9.0.2 | + + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci--with-global-session-worker (todo) + "See `test-ob-haskell-ghci--with-global-session-worker'." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (unwind-protect (funcall todo) + ;; Kill the "*haskell*" buffer to not pollute other tests. + (when-let ((hb (get-buffer "*haskell*"))) + (with-current-buffer hb + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer hb)))))) + +(defmacro test-ob-haskell-ghci-with-global-session (&rest body) + "Eval BODY in a new session, then destroy the session. +The library ob-haskell doesn't implement session yet. It will +always use a buffer named \"*haskell*\". We kill that buffer +after the source block execution. To be safe, we fail if such a +buffer already exists." + `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, don't control +which session is used (i.e. don't call +`test-ob-haskell-ghci--with-global-session-worker')." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-with-global-session (funcall todo))))) + + +;;; Tests + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Tuples +;; + +(ert-deftest ob-haskell/a-simple-tuple () + "Evaluation of tuple of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "(1,2,3)")))) + + +(ert-deftest ob-haskell/2D-tuples () + "Evaluation of nested tuples into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "((1,2,3), (4,5,6))")))) + +(ert-deftest ob-haskell/2D-tuples-multilines () + "Evaluation of nested tuples into a table, as multilines." + (should (equal '((1 2 3) (4 5 6) (7 8 9)) + (test-ob-haskell-ghci "" " +:{ +( (1,2,3) +, (4,5,6) +, (7,8,9) +) +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + +;;;; Reuse results +;; +(ert-deftest ob-haskell/reuse-table () + "Reusing a computed tables." + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" + "sum [sum r | r <- t]" + "#+name: a-table +#+begin_src haskell + [ [x..x+2] | x <- [1,4 .. 12] ] +#+end_src +")))) + + +;;;; Not define errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + (let ((r (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + (should (and r (string-match "Variable not in scope" r))))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1292 bytes --] From 9d2a08caccde50d95dc8d7b8429db757b99068b9 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 2/7] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 2b1441c2a..f4197654c 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,14 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.39.1 [-- Attachment #4: 0003-testing-lisp-test-ob-haskell-ghci.el-Fix-some-tests.patch --] [-- Type: text/x-patch, Size: 1508 bytes --] From f77135089618c8c950c15a3232ac956763f39670 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:25:19 +0100 Subject: [PATCH 3/7] * testing/lisp/test-ob-haskell-ghci.el: Fix some tests (ob-haskell/2D-lists-multilines): (ob-haskell/ghci-info): Fix incorrect test assertions. --- testing/lisp/test-ob-haskell-ghci.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index aba94d73f..e05ac3b6e 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -262,8 +262,7 @@ (ert-deftest ob-haskell/2D-lists () (ert-deftest ob-haskell/2D-lists-multilines () "Evaluation of nested lists into a table, as multilines." - :expected-result :failed - (should (equal '((1 2 3) (4 5 6)) + (should (equal '((1 2 3) (4 5 6) (7 8 9)) (test-ob-haskell-ghci "" " :{ [ [1..3] @@ -444,8 +443,9 @@ (ert-deftest ob-haskell/ghci-type () (ert-deftest ob-haskell/ghci-info () "The ghci meta command ':info' ." - (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" - (test-ob-haskell-ghci ":results output" ":info repeat")))) + (should (string-match-p + "repeat :: a -> \\[a\\][ \t]+-- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) (provide 'test-ob-haskell-ghci) -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2124 bytes --] From a71b47ceb3edb26341b2b319c862d26fbbddc6a6 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 4/7] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index e05ac3b6e..3663f21f9 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -118,7 +118,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -188,7 +187,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -200,7 +198,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -235,7 +232,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -359,7 +355,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #6: 0005-lisp-ob-haskell-Request-the-last-value-from-GHCi.patch --] [-- Type: text/x-patch, Size: 4159 bytes --] From 939c3b28a1587e0709aeadff16dc87f3dc06d120 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 09:59:31 +0100 Subject: [PATCH 5/7] lisp/ob-haskell: Request the last value from GHCi * lisp/ob-haskell.el (org-babel-interpret-haskell): When the result type is 'value, use the last value as defined by GHCi. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to output/value. --- lisp/ob-haskell.el | 32 ++++++++++++++++++++++------ testing/lisp/test-ob-haskell-ghci.el | 6 ++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index f4197654c..a8a8df9d8 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -135,12 +135,32 @@ (defun org-babel-interpret-haskell (body params) (session (org-babel-haskell-initiate-session session params)) (comint-preoutput-filter-functions (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert org-babel-haskell-eoe) - (comint-send-input nil t))) + (raw (pcase result-type + (`output + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert (org-trim full-body)) + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t))) + (`value (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (comint-send-input nil t) + (insert full-body) + (comint-send-input nil t) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil) + (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + ) + )) (results (mapcar #'org-strip-quotes (cdr (member org-babel-haskell-eoe (reverse (mapcar #'org-trim raw))))))) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 3663f21f9..2ddfb4de7 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -113,8 +113,8 @@ (ert-deftest ob-haskell/hello-world-output () (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-nothing () - :expected-result :failed - (should (equal "" + ;; GHCi prints the value on standard output. So, the last value is part of the output. + (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () @@ -416,12 +416,10 @@ (ert-deftest ob-haskell/results-value-2 () (ert-deftest ob-haskell/results-value-3 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) (ert-deftest ob-haskell/results-value-4 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" " putStrLn \"3\" return () -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #7: 0006-ob-haskell-Implement-sessions.patch --] [-- Type: text/x-patch, Size: 4755 bytes --] From b3dbcc39ab119162a9a0c5a172a33f6367a13845 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:06:44 +0100 Subject: [PATCH 6/7] ob-haskell: Implement sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Implement sessions. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to sessions. --- lisp/ob-haskell.el | 50 +++++++++++++++++++++------- testing/lisp/test-ob-haskell-ghci.el | 8 +++-- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index a8a8df9d8..f7acb1011 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -51,6 +51,8 @@ (declare-function haskell-mode "ext:haskell-mode" ()) (declare-function run-haskell "ext:inf-haskell" (&optional arg)) (declare-function inferior-haskell-load-file "ext:inf-haskell" (&optional reload)) +(declare-function inferior-haskell-start-process + "ext:inf-haskell" ()) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (defvar org-babel-tangle-lang-exts) @@ -183,20 +185,44 @@ (defun org-babel-execute:haskell (body params) (org-babel-interpret-haskell body params) (org-babel-haskell-execute body params)))) -(defun org-babel-haskell-initiate-session (&optional _session _params) + + + +;; Variable defined in inf-haskell (haskell-mode package). +(defvar inferior-haskell-buffer) + +(defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -If there is not a current inferior-process-buffer in SESSION -then create one. Return the initialized session." +Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") - (or (get-buffer "*haskell*") - (save-window-excursion - (run-haskell) - (sleep-for 0.25) - ;; Disable secondary prompt. - (org-babel-comint-input-command - (current-buffer) - ":set prompt-cont \"\"") - (current-buffer)))) + (when (and session-name (string= session-name "none")) + (setq session-name nil)) + (unless session-name + ;; As haskell-mode is using the buffer name "*haskell*", we stay + ;; away from it. + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) + (let ((session (get-buffer session-name))) + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (let ((inferior-haskell-buffer session)) + (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) + (with-current-buffer session (rename-buffer "*haskell*"))) + (save-window-excursion + ;; We don't use `run-haskell' to not popup the buffer. + ;; And we protect default-directory. + (let ((default-directory default-directory)) + (inferior-haskell-start-process)) + (sleep-for 0.25) + (setq session inferior-haskell-buffer) + (with-current-buffer session (rename-buffer session-name)) + ;; Disable secondary prompt. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session)))) + session)) + (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 2ddfb4de7..907487960 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -133,7 +133,6 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) @@ -143,11 +142,16 @@ (ert-deftest ob-haskell/sessions-must-not-share-variables () (ert-deftest ob-haskell/no-session-means-one-shot-sessions () "When no session, use a new session." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci "" "x=2" nil :unprotected) (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) +(ert-deftest ob-haskell/reuse-variables-in-same-session () + "Reuse variables between blocks using the same session." + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) + + ;;;; Values ;; -- 2.39.1 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #8: 0007-ob-haskell-Update-tests-about-errors.patch --] [-- Type: text/x-patch, Size: 1489 bytes --] From bd99c392e7c4aecdd94a636b2cc05ee55b681154 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:09:26 +0100 Subject: [PATCH 7/7] ob-haskell: Update tests about errors testing/lisp/test-ob-haskell-ghci.el: Update tests about errors. --- testing/lisp/test-ob-haskell-ghci.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 907487960..54705852f 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -344,18 +344,20 @@ (ert-deftest ob-haskell/reuse-table () (ert-deftest ob-haskell/not-defined () "Evaluation of undefined variables." - (should (string-match "Variable not in scope" - (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + :expected-result :failed + (should-error (test-ob-haskell-ghci "" "notDefined :: IO Int"))) + (ert-deftest ob-haskell/not-defined-then-defined-1 () "Evaluation of undefined variables. This is a valid haskell source, but, invalid when entered one line at a time in GHCi." - (let ((r (test-ob-haskell-ghci "" " + :expected-result :failed + (should-error (test-ob-haskell-ghci "" " v :: Int v = 4 "))) - (should (and r (string-match "Variable not in scope" r))))) + (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." -- 2.39.1 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-25 10:01 ` Bruno Barbier @ 2023-03-26 9:09 ` Ihor Radchenko 2023-03-26 9:40 ` Bruno Barbier [not found] ` <notmuch-sha1-0807e1720f829950d42ef560bc30e56bd152766c> 0 siblings, 2 replies; 26+ messages in thread From: Ihor Radchenko @ 2023-03-26 9:09 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: > About :results output vs :results value, I decided to use the "it" > variable, i.e. to ask GHCi what was the last value (see [1]). > > About sessions, I decided to rename the buffer as you suggested. That's > a partial workaround that should work well enough, until haskell-mode > provides a way to choose the buffer name. > > I've updated the tests and the expected results. Thanks! I will provide comments inline. > Note that I've changed the tests about errors; I'm now expecting > ob-haskell to raise errors. I'm not sure what we should expect to be > consistent with other org babel backends. Errors are usually displayed separately, using `org-babel-eval-error-notify'. > +;; To increase the chances of failure when running tests, you can use this command line: > +;; > +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED > +;; > + > +;;;; Status > +;; > +;; All the tests should succeed (except for random failures); those > +;; flagged with ":expected-result :failed" are known > +;; limitations/bugs. Tested with (2023-03-18): > +;; > +;; | emacs-version | 29.0.60 | > +;; | org-version | main@4cad6c8ea (Mar 16 2023) | > +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | > +;; | ghci | 9.0.2 | You can probably remove this. > + (`value (org-babel-comint-with-output > + (session org-babel-haskell-eoe nil full-body) > + (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") > + (comint-send-input nil t) > + (insert full-body) > + (comint-send-input nil t) > + (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") > + (comint-send-input nil t) > + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) Why not simply putStrLn ("\"" ++ show it ++ "\"") ? > + (when (and session-name (string= session-name "none")) > + (setq session-name nil)) > + (unless session-name > + ;; As haskell-mode is using the buffer name "*haskell*", we stay > + ;; away from it. > + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) This will make ob-haskell spawn a separate ghci process buffer every single time a user runs non-session src block. And the buffer is not closed after getting the result. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-26 9:09 ` Ihor Radchenko @ 2023-03-26 9:40 ` Bruno Barbier 2023-03-26 9:46 ` Ihor Radchenko [not found] ` <notmuch-sha1-0807e1720f829950d42ef560bc30e56bd152766c> 1 sibling, 1 reply; 26+ messages in thread From: Bruno Barbier @ 2023-03-26 9:40 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode Ihor Radchenko <yantar92@posteo.net> writes: > Bruno Barbier <brubar.cs@gmail.com> writes: > >> Note that I've changed the tests about errors; I'm now expecting >> ob-haskell to raise errors. I'm not sure what we should expect to be >> consistent with other org babel backends. > > Errors are usually displayed separately, using > `org-babel-eval-error-notify'. I'll see what can be done with GHCi and use this if possible. Thanks. >> +;; To increase the chances of failure when running tests, you can use this command line: >> +;; >> +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED >> +;; >> + >> +;;;; Status >> +;; >> +;; All the tests should succeed (except for random failures); those >> +;; flagged with ":expected-result :failed" are known >> +;; limitations/bugs. Tested with (2023-03-18): >> +;; >> +;; | emacs-version | 29.0.60 | >> +;; | org-version | main@4cad6c8ea (Mar 16 2023) | >> +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | >> +;; | ghci | 9.0.2 | > > You can probably remove this. Definitely. I'll do. Thanks. >> + (`value (org-babel-comint-with-output >> + (session org-babel-haskell-eoe nil full-body) >> + (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") >> + (comint-send-input nil t) >> + (insert full-body) >> + (comint-send-input nil t) >> + (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") >> + (comint-send-input nil t) >> + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) > > Why not simply putStrLn ("\"" ++ show it ++ "\"") ? > I'm not sure I understand. I'm using the first 'org-babel-comint-with-output' to execute the source block and save the last value (the "it" variable). Then, I'm using a second 'org-babel-comint-with-output' to make sure the output of this one contains only the last value. If I display "it" in the first block, I will not be able to differentiate between some output, previous values and the last value. And, printing "org-babel-haskell-eoe" updates the "it" variable (the last value becoming "()"), so I have to store the real "it" somewhere. Am I missing something ? >> + (when (and session-name (string= session-name "none")) >> + (setq session-name nil)) >> + (unless session-name >> + ;; As haskell-mode is using the buffer name "*haskell*", we stay >> + ;; away from it. >> + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) > > This will make ob-haskell spawn a separate ghci process buffer every > single time a user runs non-session src block. And the buffer is not > closed after getting the result. Very good point! I will update this to use the same buffer named "*ob-haskell*" when the user doesn't set the session name. I guess it's consistent with other org-babel backends. Thank you very much for the review and you help! Bruno > > -- > Ihor Radchenko // yantar92, > Org mode contributor, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-26 9:40 ` Bruno Barbier @ 2023-03-26 9:46 ` Ihor Radchenko 0 siblings, 0 replies; 26+ messages in thread From: Ihor Radchenko @ 2023-03-26 9:46 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: >> Why not simply putStrLn ("\"" ++ show it ++ "\"") ? >> > > I'm not sure I understand. I'm using the first > 'org-babel-comint-with-output' to execute the source block and save the > last value (the "it" variable). Then, I'm using a second > 'org-babel-comint-with-output' to make sure the output of this one > contains only the last value. If I display "it" in the first block, I > will not be able to differentiate between some output, previous values > and the last value. And, printing "org-babel-haskell-eoe" updates the > "it" variable (the last value becoming "()"), so I have to store the > real "it" somewhere. I see. I missed that `it' gets updated because of eoe indicator. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
[parent not found: <notmuch-sha1-0807e1720f829950d42ef560bc30e56bd152766c>]
* Re: [PATCH] Add tests for ob-haskell (GHCi) [not found] ` <notmuch-sha1-0807e1720f829950d42ef560bc30e56bd152766c> @ 2023-05-07 8:50 ` Bruno Barbier 2023-05-07 9:18 ` Ruijie Yu via General discussions about Org-mode. 2023-05-08 10:59 ` Ihor Radchenko 0 siblings, 2 replies; 26+ messages in thread From: Bruno Barbier @ 2023-05-07 8:50 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 1734 bytes --] Hi Ihor, Sorry for the delay. Bruno Barbier <brubar.cs@gmail.com> writes: > Ihor Radchenko <yantar92@posteo.net> writes: > >> Bruno Barbier <brubar.cs@gmail.com> writes: >> >>> Note that I've changed the tests about errors; I'm now expecting >>> ob-haskell to raise errors. I'm not sure what we should expect to be >>> consistent with other org babel backends. >> >> Errors are usually displayed separately, using >> `org-babel-eval-error-notify'. > > I'll see what can be done with GHCi and use this if possible. Unfortunately, no progress on this. And I don't really have time to work on this. >>> +;; To increase the chances of failure when running tests, you can use this command line: >>> +;; .. >>> +;; | ghci | 9.0.2 | >> >> You can probably remove this. > > Definitely. I'll do. Thanks. Done. >>> + (when (and session-name (string= session-name "none")) >>> + (setq session-name nil)) >>> + (unless session-name >>> + ;; As haskell-mode is using the buffer name "*haskell*", we stay >>> + ;; away from it. >>> + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) >> >> This will make ob-haskell spawn a separate ghci process buffer every >> single time a user runs non-session src block. And the buffer is not >> closed after getting the result. > Very good point! > > I will update this to use the same buffer named "*ob-haskell*" when the > user doesn't set the session name. I guess it's consistent with other > org-babel backends. I've changed the way session works. 'ob-haskell' should now destroy temporary sessions. Thanks again for spotting that mistake. Let me know if you see further improvement before pushing this. Thanks, Bruno [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 15045 bytes --] From 9ef867cd2cf89e77b5c5a5a7090fd37b1702e06a Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 01/13] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 453 +++++++++++++++++++++++++++ 1 file changed, 453 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..aba94d73f --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,453 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. + +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html +;;;; FIXME: Random failures +;; +;; To increase the chances of failure when running tests, you can use this command line: +;; +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED +;; + +;;;; Status +;; +;; All the tests should succeed (except for random failures); those +;; flagged with ":expected-result :failed" are known +;; limitations/bugs. Tested with (2023-03-18): +;; +;; | emacs-version | 29.0.60 | +;; | org-version | main@4cad6c8ea (Mar 16 2023) | +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | +;; | ghci | 9.0.2 | + + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci--with-global-session-worker (todo) + "See `test-ob-haskell-ghci--with-global-session-worker'." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (unwind-protect (funcall todo) + ;; Kill the "*haskell*" buffer to not pollute other tests. + (when-let ((hb (get-buffer "*haskell*"))) + (with-current-buffer hb + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer hb)))))) + +(defmacro test-ob-haskell-ghci-with-global-session (&rest body) + "Eval BODY in a new session, then destroy the session. +The library ob-haskell doesn't implement session yet. It will +always use a buffer named \"*haskell*\". We kill that buffer +after the source block execution. To be safe, we fail if such a +buffer already exists." + `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, don't control +which session is used (i.e. don't call +`test-ob-haskell-ghci--with-global-session-worker')." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-with-global-session (funcall todo))))) + + +;;; Tests + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Tuples +;; + +(ert-deftest ob-haskell/a-simple-tuple () + "Evaluation of tuple of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "(1,2,3)")))) + + +(ert-deftest ob-haskell/2D-tuples () + "Evaluation of nested tuples into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "((1,2,3), (4,5,6))")))) + +(ert-deftest ob-haskell/2D-tuples-multilines () + "Evaluation of nested tuples into a table, as multilines." + (should (equal '((1 2 3) (4 5 6) (7 8 9)) + (test-ob-haskell-ghci "" " +:{ +( (1,2,3) +, (4,5,6) +, (7,8,9) +) +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + +;;;; Reuse results +;; +(ert-deftest ob-haskell/reuse-table () + "Reusing a computed tables." + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" + "sum [sum r | r <- t]" + "#+name: a-table +#+begin_src haskell + [ [x..x+2] | x <- [1,4 .. 12] ] +#+end_src +")))) + + +;;;; Not define errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + (let ((r (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + (should (and r (string-match "Variable not in scope" r))))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1294 bytes --] From 9972b926f55cb970e0b520f8726a3684118017b6 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 02/13] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 909de19ab..500be89a2 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,14 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #4: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2126 bytes --] From 352d18399961fedc45cc2d64007016426e1ecd40 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 04/13] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index e05ac3b6e..3663f21f9 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -118,7 +118,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -188,7 +187,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -200,7 +198,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -235,7 +232,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -359,7 +355,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0005-lisp-ob-haskell-Request-the-last-value-from-GHCi.patch --] [-- Type: text/x-patch, Size: 4161 bytes --] From 0fa579016222f3bf90672e9700d6b9b4eecd5c2c Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 09:59:31 +0100 Subject: [PATCH 05/13] lisp/ob-haskell: Request the last value from GHCi * lisp/ob-haskell.el (org-babel-interpret-haskell): When the result type is 'value, use the last value as defined by GHCi. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to output/value. --- lisp/ob-haskell.el | 32 ++++++++++++++++++++++------ testing/lisp/test-ob-haskell-ghci.el | 6 ++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 500be89a2..961ae9c8a 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -135,12 +135,32 @@ (defun org-babel-interpret-haskell (body params) (session (org-babel-haskell-initiate-session session params)) (comint-preoutput-filter-functions (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert org-babel-haskell-eoe) - (comint-send-input nil t))) + (raw (pcase result-type + (`output + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert (org-trim full-body)) + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t))) + (`value (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (comint-send-input nil t) + (insert full-body) + (comint-send-input nil t) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil) + (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + ) + )) (results (mapcar #'org-strip-quotes (cdr (member org-babel-haskell-eoe (reverse (mapcar #'org-trim raw))))))) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 3663f21f9..2ddfb4de7 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -113,8 +113,8 @@ (ert-deftest ob-haskell/hello-world-output () (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-nothing () - :expected-result :failed - (should (equal "" + ;; GHCi prints the value on standard output. So, the last value is part of the output. + (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () @@ -416,12 +416,10 @@ (ert-deftest ob-haskell/results-value-2 () (ert-deftest ob-haskell/results-value-3 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) (ert-deftest ob-haskell/results-value-4 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" " putStrLn \"3\" return () -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #6: 0006-ob-haskell-Implement-sessions.patch --] [-- Type: text/x-patch, Size: 4757 bytes --] From 7d66cff5cc23bb786cb2843f4326d2869512ccac Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:06:44 +0100 Subject: [PATCH 06/13] ob-haskell: Implement sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Implement sessions. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to sessions. --- lisp/ob-haskell.el | 50 +++++++++++++++++++++------- testing/lisp/test-ob-haskell-ghci.el | 8 +++-- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 961ae9c8a..6bbc91439 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -51,6 +51,8 @@ (declare-function haskell-mode "ext:haskell-mode" ()) (declare-function run-haskell "ext:inf-haskell" (&optional arg)) (declare-function inferior-haskell-load-file "ext:inf-haskell" (&optional reload)) +(declare-function inferior-haskell-start-process + "ext:inf-haskell" ()) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (defvar org-babel-tangle-lang-exts) @@ -183,20 +185,44 @@ (defun org-babel-execute:haskell (body params) (org-babel-interpret-haskell body params) (org-babel-haskell-execute body params)))) -(defun org-babel-haskell-initiate-session (&optional _session _params) + + + +;; Variable defined in inf-haskell (haskell-mode package). +(defvar inferior-haskell-buffer) + +(defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -If there is not a current inferior-process-buffer in SESSION -then create one. Return the initialized session." +Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") - (or (get-buffer "*haskell*") - (save-window-excursion - (run-haskell) - (sleep-for 0.25) - ;; Disable secondary prompt. - (org-babel-comint-input-command - (current-buffer) - ":set prompt-cont \"\"") - (current-buffer)))) + (when (and session-name (string= session-name "none")) + (setq session-name nil)) + (unless session-name + ;; As haskell-mode is using the buffer name "*haskell*", we stay + ;; away from it. + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) + (let ((session (get-buffer session-name))) + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (let ((inferior-haskell-buffer session)) + (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) + (with-current-buffer session (rename-buffer "*haskell*"))) + (save-window-excursion + ;; We don't use `run-haskell' to not popup the buffer. + ;; And we protect default-directory. + (let ((default-directory default-directory)) + (inferior-haskell-start-process)) + (sleep-for 0.25) + (setq session inferior-haskell-buffer) + (with-current-buffer session (rename-buffer session-name)) + ;; Disable secondary prompt. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session)))) + session)) + (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 2ddfb4de7..907487960 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -133,7 +133,6 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) @@ -143,11 +142,16 @@ (ert-deftest ob-haskell/sessions-must-not-share-variables () (ert-deftest ob-haskell/no-session-means-one-shot-sessions () "When no session, use a new session." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci "" "x=2" nil :unprotected) (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) +(ert-deftest ob-haskell/reuse-variables-in-same-session () + "Reuse variables between blocks using the same session." + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) + + ;;;; Values ;; -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #7: 0007-ob-haskell-Update-tests-about-errors.patch --] [-- Type: text/x-patch, Size: 1491 bytes --] From 1fb557ff8c4b594735e1d00b236a33c5a70fc82e Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:09:26 +0100 Subject: [PATCH 07/13] ob-haskell: Update tests about errors testing/lisp/test-ob-haskell-ghci.el: Update tests about errors. --- testing/lisp/test-ob-haskell-ghci.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 907487960..54705852f 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -344,18 +344,20 @@ (ert-deftest ob-haskell/reuse-table () (ert-deftest ob-haskell/not-defined () "Evaluation of undefined variables." - (should (string-match "Variable not in scope" - (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + :expected-result :failed + (should-error (test-ob-haskell-ghci "" "notDefined :: IO Int"))) + (ert-deftest ob-haskell/not-defined-then-defined-1 () "Evaluation of undefined variables. This is a valid haskell source, but, invalid when entered one line at a time in GHCi." - (let ((r (test-ob-haskell-ghci "" " + :expected-result :failed + (should-error (test-ob-haskell-ghci "" " v :: Int v = 4 "))) - (should (and r (string-match "Variable not in scope" r))))) + (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #8: 0008-testing-lisp-test-ob-haskell-ghci.el-Cleanup-comment.patch --] [-- Type: text/x-patch, Size: 1495 bytes --] From 13b4147fc5cf9ca9dfc81909a9b9e55cb8bf1a60 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 1 Apr 2023 10:00:30 +0200 Subject: [PATCH 08/13] * testing/lisp/test-ob-haskell-ghci.el: Cleanup comments --- testing/lisp/test-ob-haskell-ghci.el | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 54705852f..b452a55dd 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -24,24 +24,6 @@ ;; ;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html ;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html -;;;; FIXME: Random failures -;; -;; To increase the chances of failure when running tests, you can use this command line: -;; -;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED -;; - -;;;; Status -;; -;; All the tests should succeed (except for random failures); those -;; flagged with ":expected-result :failed" are known -;; limitations/bugs. Tested with (2023-03-18): -;; -;; | emacs-version | 29.0.60 | -;; | org-version | main@4cad6c8ea (Mar 16 2023) | -;; | haskell-mode | master@20d4e23 (Mar 4 2023) | -;; | ghci | 9.0.2 | - ;;; Code: ;; -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #9: 0009-lisp-ob-haskell.el-Simplify-org-babel-haskell-eoe.patch --] [-- Type: text/x-patch, Size: 2482 bytes --] From 6e1440613b7b19c90beed91ed20fd51aac500e65 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 1 Apr 2023 10:19:24 +0200 Subject: [PATCH 09/13] lisp/ob-haskell.el: Simplify org-babel-haskell-eoe lisp/ob-haskell.el (org-babel-haskell-eoe): New default value. (org-babel-interpret-haskell): Update for the new value of `org-babel-haskell-eoe'. --- lisp/ob-haskell.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 6bbc91439..98b1b10f0 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -63,7 +63,7 @@ (defvar org-babel-default-header-args:haskell (defvar org-babel-haskell-lhs2tex-command "lhs2tex") -(defvar org-babel-haskell-eoe "\"org-babel-haskell-eoe\"") +(defvar org-babel-haskell-eoe "org-babel-haskell-eoe") (defvar haskell-prompt-regexp) @@ -143,7 +143,7 @@ (defun org-babel-interpret-haskell (body params) (session org-babel-haskell-eoe nil full-body) (insert (org-trim full-body)) (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t))) (`value (org-babel-comint-with-output (session org-babel-haskell-eoe nil full-body) @@ -153,13 +153,13 @@ (defun org-babel-interpret-haskell (body params) (comint-send-input nil t) (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t)) (org-babel-comint-with-output (session org-babel-haskell-eoe nil) (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t)) ) )) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #10: 0010-testing-lisp-test-ob-haskell-ghci.el-Test-output-wit.patch --] [-- Type: text/x-patch, Size: 1817 bytes --] From 78d7e59f71646af0871ce40368193745431caf1c Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:27:57 +0200 Subject: [PATCH 10/13] * testing/lisp/test-ob-haskell-ghci.el: Test output without EOL (ob-haskell/output-without-eol-1): (ob-haskell/output-without-eol-2): (ob-haskell/output-without-eol-3): New tests. --- testing/lisp/test-ob-haskell-ghci.el | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index b452a55dd..49d5a8684 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -165,6 +165,38 @@ (ert-deftest ob-haskell/eval-strings () "Evaluation of strings." (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) +;;;; Output without EOL +;; + +(ert-deftest ob-haskell/output-without-eol-1 () + "Cannot get output from incomplete lines, when entered line by line." + :expected-result :failed + (should (equal "123" + (test-ob-haskell-ghci ":results output" " + putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +")))) + +(ert-deftest ob-haskell/output-without-eol-2 () + "Incomplete output lines are OK when using a multiline block." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +:{ + do putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +:} +")))) + +(ert-deftest ob-haskell/output-without-eol-3 () + "Incomplete output lines are OK on one line." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +do { putStr(\"1\"); putStr(\"2\"); putStr(\"3\"); putStr(\"\\n\") } +")))) ;;;; Local variables (ert-deftest ob-haskell/let-one-line () -- 2.39.3 [-- Attachment #11: 0011-lisp-ob-haskell.el-Fix-how-to-use-sessions.patch --] [-- Type: text/x-patch, Size: 11477 bytes --] From f3feae865b37649e44261f83bf45d925e5f6cea0 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:43:16 +0200 Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Redesign how to handle session names. (org-babel-haskell-with-session): New function to manage sessions. (org-babel-interpret-haskell): Refactor code. Use `org-babel-haskell-with-session` to manage sessions. (org-babel-prep-session:haskell): Don't ignore the PARAMS argument. --- lisp/ob-haskell.el | 182 +++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 72 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 98b1b10f0..deaa434f8 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -129,54 +129,58 @@ (defun org-babel-interpret-haskell (body params) (lambda () (setq-local comint-prompt-regexp (concat haskell-prompt-regexp "\\|^λ?> ")))) - (let* ((session (cdr (assq :session params))) - (result-type (cdr (assq :result-type params))) - (full-body (org-babel-expand-body:generic - body params - (org-babel-variable-assignments:haskell params))) - (session (org-babel-haskell-initiate-session session params)) - (comint-preoutput-filter-functions - (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (pcase result-type - (`output - (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t))) - (`value (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") - (comint-send-input nil t) - (insert full-body) - (comint-send-input nil t) - (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t)) - (org-babel-comint-with-output - (session org-babel-haskell-eoe nil) - (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t)) - ) - )) - (results (mapcar #'org-strip-quotes - (cdr (member org-babel-haskell-eoe - (reverse (mapcar #'org-trim raw))))))) - (org-babel-reassemble-table - (let ((result - (pcase result-type - (`output (mapconcat #'identity (reverse results) "\n")) - (`value (car results))))) - (org-babel-result-cond (cdr (assq :result-params params)) - result (when result (org-babel-script-escape result)))) - (org-babel-pick-name (cdr (assq :colname-names params)) - (cdr (assq :colname-names params))) - (org-babel-pick-name (cdr (assq :rowname-names params)) - (cdr (assq :rowname-names params)))))) + (org-babel-haskell-with-session + params + (lambda (session) + (cl-labels + ((csend (txt) + (insert txt) (comint-send-input nil t)) + (eom () + (csend (concat "putStrLn \"" org-babel-haskell-eoe "\"\n"))) + (with-output (todo) + (let ((comint-preoutput-filter-functions + (cons 'ansi-color-filter-apply + comint-preoutput-filter-functions))) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil nil) + (funcall todo))))) + (let* ((result-type (cdr (assq :result-type params))) + (full-body (org-babel-expand-body:generic + body params + (org-babel-variable-assignments:haskell params))) + (raw (pcase result-type + (`output + (with-output + (lambda () (csend (org-trim full-body)) (eom)))) + (`value + ;; We first compute the value and store the + ;; value, ignoring any output. + (with-output + (lambda () + (csend "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (csend (org-trim full-body)) + (csend "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (eom))) + ;; We now display and capture the value. + (with-output + (lambda() + (csend "__LAST_VALUE_IMPROBABLE_NAME__\n") + (eom)))))) + (results (mapcar #'org-strip-quotes + (cdr (member org-babel-haskell-eoe + (reverse (mapcar #'org-trim raw))))))) + (org-babel-reassemble-table + (let ((result + (pcase result-type + (`output (mapconcat #'identity (reverse results) "\n")) + (`value (car results))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (when result (org-babel-script-escape result)))) + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colname-names params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rowname-names params))))))))) + (defun org-babel-execute:haskell (body params) "Execute a block of Haskell code." @@ -186,6 +190,23 @@ (defun org-babel-execute:haskell (body params) (org-babel-haskell-execute body params)))) +(defun org-babel-haskell-with-session (params todo) + "Call TODO with a suitable session buffer. +Use PARAMS to get/create/destroy the session as needed. +Return the result of the call." + (let* ((sn (cdr (assq :session params))) + (session (org-babel-haskell-initiate-session sn params)) + (one-shot (equal sn "none"))) + (unwind-protect + (funcall todo session) + (when (and one-shot (buffer-live-p session)) + ;; As we don't control how the session temporary buffer is + ;; created, we need to explicitly work around the hooks and + ;; query functions. + (with-current-buffer session + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer session))))))) ;; Variable defined in inf-haskell (haskell-mode package). @@ -193,34 +214,51 @@ (defvar inferior-haskell-buffer) (defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -Return the initialized session." +Return the initialized session, i.e. the buffer for this session. +When SESSION-NAME is nil, use a global session named +\"*ob-haskell*\". When SESSION-NAME is the string \"none\", use +a temporary buffer. Else, (re)use the session named +SESSION-NAME. The buffer name is the session name. See also +`org-babel-haskell-with-session'." (org-require-package 'inf-haskell "haskell-mode") - (when (and session-name (string= session-name "none")) - (setq session-name nil)) - (unless session-name - ;; As haskell-mode is using the buffer name "*haskell*", we stay - ;; away from it. - (setq session-name (generate-new-buffer-name "*ob-haskell*"))) + (cond + ((equal "none" session-name) + ;; Temporary buffer name. + (setq session-name (generate-new-buffer-name " *ob-haskell-tmp*"))) + ((eq nil session-name) + ;; The global default session. As haskell-mode is using the buffer + ;; named "*haskell*", we stay away from it. + (setq session-name "*ob-haskell*"))) (let ((session (get-buffer session-name))) (save-window-excursion (or (org-babel-comint-buffer-livep session) (let ((inferior-haskell-buffer session)) - (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) - (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) - (with-current-buffer session (rename-buffer "*haskell*"))) - (save-window-excursion - ;; We don't use `run-haskell' to not popup the buffer. - ;; And we protect default-directory. - (let ((default-directory default-directory)) - (inferior-haskell-start-process)) - (sleep-for 0.25) - (setq session inferior-haskell-buffer) - (with-current-buffer session (rename-buffer session-name)) - ;; Disable secondary prompt. - (org-babel-comint-input-command - session - ":set prompt-cont \"\"") - session)))) + ;; As inferior-haskell expects the buffer to be named + ;; "*haskell*", we rename it, unless the user explicitly + ;; requested to use the name "*haskell*". + (when (not (equal "*haskell*" session-name)) + (when (and (bufferp session) + (not (org-babel-comint-buffer-livep session))) + (when (bufferp "*haskell*") + (user-error "Conflicting buffer '*haskell*', rename it or kill it")) + (with-current-buffer session (rename-buffer "*haskell*")))) + (unwind-protect + (save-window-excursion + ;; We don't use `run-haskell' to not popup the buffer. + ;; And we protect default-directory. + (let ((default-directory default-directory)) + (inferior-haskell-start-process)) + (sleep-for 0.25) + (setq session inferior-haskell-buffer)) + (when (and (not (equal "*haskell*" session-name)) + (bufferp session)) + (with-current-buffer session (rename-buffer session-name)))) + ;; Disable secondary prompt. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session) + )) session)) @@ -237,7 +275,7 @@ (defun org-babel-load-session:haskell (session body params) (defun org-babel-prep-session:haskell (session params) "Prepare SESSION according to the header arguments in PARAMS." (save-window-excursion - (let ((buffer (org-babel-haskell-initiate-session session))) + (let ((buffer (org-babel-haskell-initiate-session session params))) (org-babel-comint-in-buffer buffer (mapc (lambda (line) (insert line) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #12: 0012-testing-lisp-test-ob-haskell-ghci.el-Modify-test-ob-.patch --] [-- Type: text/x-patch, Size: 3471 bytes --] From d27a381cb0afb07a1a0a043c820e1eae86570ad5 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 11:06:45 +0200 Subject: [PATCH 12/13] * testing/lisp/test-ob-haskell-ghci.el: Modify `test-ob-haskell-ghci` * testing/lisp/test-ob-haskell-ghci.el (test-ob-haskell-ghci--with-global-session-worker) (test-ob-haskell-ghci-with-global-session): Deleted. (test-ob-haskell-ghci-checking-buffers): New function. (test-ob-haskell-ghci): Update to handle the new meaning of sessions. --- testing/lisp/test-ob-haskell-ghci.el | 40 +++++++++++----------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 49d5a8684..b356f4951 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -37,43 +37,33 @@ (unless (featurep 'haskell-mode) ;;; Helpers ;; -(defun test-ob-haskell-ghci--with-global-session-worker (todo) +(defun test-ob-haskell-ghci-checking-buffers (todo) "See `test-ob-haskell-ghci--with-global-session-worker'." (when (get-buffer "*haskell*") (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) - (unwind-protect (funcall todo) - ;; Kill the "*haskell*" buffer to not pollute other tests. + (prog1 (funcall todo) (when-let ((hb (get-buffer "*haskell*"))) - (with-current-buffer hb - (let ((kill-buffer-query-functions nil) - (kill-buffer-hook nil)) - (kill-buffer hb)))))) - -(defmacro test-ob-haskell-ghci-with-global-session (&rest body) - "Eval BODY in a new session, then destroy the session. -The library ob-haskell doesn't implement session yet. It will -always use a buffer named \"*haskell*\". We kill that buffer -after the source block execution. To be safe, we fail if such a -buffer already exists." - `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + ;; We created a "*haskell*" buffer. That shouldn't happen. + (error "'ob-haskell' created a buffer named '*haskell*'")))) + + (defun test-ob-haskell-ghci (args content &optional preamble unprotected) "Execute the code block CONTENT in a new GHCi session; return the result. Add ARGS to the code block argument line. Insert PREAMBLE -before the code block. When UNPROTECTED is non-nil, don't control -which session is used (i.e. don't call -`test-ob-haskell-ghci--with-global-session-worker')." +before the code block. When UNPROTECTED is non-nil, check pre/post conditions." (when (listp content) (setq content (string-join content "\n"))) (unless preamble (setq preamble "")) - (let ((todo (lambda () - (org-test-with-temp-text - (concat preamble "\n" "#+begin_src haskell :compile no " - args "\n" "<point>" content "\n#+end_src") - (org-babel-execute-src-block))))) - (if unprotected (funcall todo) - (test-ob-haskell-ghci-with-global-session (funcall todo))))) + (let ((todo (lambda () + (prog1 (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block)))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-checking-buffers todo)))) + ;;; Tests -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #13: 0013-testing-lisp-test-ob-haskell-ghci.el-Update-session-.patch --] [-- Type: text/x-patch, Size: 3132 bytes --] From 17d0d31fbc53876831ee82049a3a7f1f533924b8 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 11:10:42 +0200 Subject: [PATCH 13/13] * testing/lisp/test-ob-haskell-ghci.el: Update session tests * testing/lisp/test-ob-haskell-ghci.el (ob-haskell/no-session-means-one-shot-sessions): Deleted. (ob-haskell/session-named-none-means-one-shot-sessions): New test. (ob-haskell/sessions-must-not-share-variables): Rewrite tests to match the new meaning of session names. (ob-haskell/may-use-the-*haskell*-session): New test. --- testing/lisp/test-ob-haskell-ghci.el | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index b356f4951..1c9c4c65f 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -105,24 +105,38 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) - (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) - (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - )) - -(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil))) + (test-ob-haskell-ghci ":session s2" "x=3" nil) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil))) + ) + +(ert-deftest ob-haskell/session-named-none-means-one-shot-sessions () "When no session, use a new session." - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci "" "x=2" nil :unprotected) - (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil)))) (ert-deftest ob-haskell/reuse-variables-in-same-session () "Reuse variables between blocks using the same session." (test-ob-haskell-ghci ":session s1" "x=2" nil) (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) +(ert-deftest ob-haskell/may-use-the-*haskell*-session () + "The user may use the special *haskell* buffer." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't run this test")) + (unwind-protect + (progn + (test-ob-haskell-ghci ":session *haskell*" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session *haskell*" "x" nil :unprotected)))) + (with-current-buffer "*haskell*" + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer "*haskell*"))))) + + ;;;; Values -- 2.39.3 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-05-07 8:50 ` Bruno Barbier @ 2023-05-07 9:18 ` Ruijie Yu via General discussions about Org-mode. 2023-05-07 11:15 ` Bruno Barbier 2023-05-08 10:59 ` Ihor Radchenko 1 sibling, 1 reply; 26+ messages in thread From: Ruijie Yu via General discussions about Org-mode. @ 2023-05-07 9:18 UTC (permalink / raw) To: Bruno Barbier; +Cc: Ihor Radchenko, emacs-orgmode Minor remarks below regarding the patchset. Bruno Barbier <brubar.cs@gmail.com> writes: > From 9ef867cd2cf89e77b5c5a5a7090fd37b1702e06a Mon Sep 17 00:00:00 2001 > From: Bruno BARBIER <brubar.cs@gmail.com> > Date: Fri, 18 Nov 2022 20:14:20 +0100 > Subject: [PATCH 01/13] ob-haskell: Add tests for GHCi > > testing/lisp/test-ob-haskell-ghci.el: New file. > --- > testing/lisp/test-ob-haskell-ghci.el | 453 +++++++++++++++++++++++++++ > 1 file changed, 453 insertions(+) > create mode 100644 testing/lisp/test-ob-haskell-ghci.el > > diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el > new file mode 100644 > index 000000000..aba94d73f > --- /dev/null > +++ b/testing/lisp/test-ob-haskell-ghci.el > @@ -0,0 +1,453 @@ > +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- > + > +;; Copyright (c) 2023 Free Software Foundation, Inc. lisp/org.el has only a single space, so probably single space here as well. > + > +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> > + > +;; This program is free software; you can redistribute it and/or modify > +;; it under the terms of the GNU General Public License as published by > +;; the Free Software Foundation, either version 3 of the License, or > +;; (at your option) any later version. Do we need the text for "part of GNU Emacs"? > + > +;; This program is distributed in the hope that it will be useful, > +;; but WITHOUT ANY WARRANTY; without even the implied warranty of > +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the > +;; GNU General Public License for more details. > + > +;; You should have received a copy of the GNU General Public License > +;; along with this program. If not, see <https://www.gnu.org/licenses/>. > + > +;;; Commentary: > +;; > + > +;;;; Useful references > +;; > +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html > +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html > +;;;; FIXME: Random failures > +;; > +;; To increase the chances of failure when running tests, you can use this command line: > +;; > +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED > +;; Wanted to say something here, but then realized that you deleted this portion in a later commit. > + > +;;;; Status > +;; > +;; All the tests should succeed (except for random failures); those > +;; flagged with ":expected-result :failed" are known > +;; limitations/bugs. Tested with (2023-03-18): > +;; > +;; | emacs-version | 29.0.60 | > +;; | org-version | main@4cad6c8ea (Mar 16 2023) | > +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | > +;; | ghci | 9.0.2 | > + > + > +;;; Code: > +;; > + > +(require 'org-test "../testing/org-test") > +(org-test-for-executable "ghci") > +(unless (featurep 'haskell-mode) > + (signal 'missing-test-dependency "haskell-mode")) > + > + > +;;; Helpers > +;; > + > +(defun test-ob-haskell-ghci--with-global-session-worker (todo) > + "See `test-ob-haskell-ghci--with-global-session-worker'." This docstring doesn't say much and only refers to itself. Maybe explain what it does? (Or now that I look at it, potentially you wanted to refer to the macro `test-ob-haskell-ghci-with-global-session' instead.) > +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) > + "Execute the code block CONTENT in a new GHCi session; return the result. > +Add ARGS to the code block argument line. Insert PREAMBLE > +before the code block. When UNPROTECTED is non-nil, don't control > +which session is used (i.e. don't call > +`test-ob-haskell-ghci--with-global-session-worker')." > + (when (listp content) > + (setq content (string-join content "\n"))) > + (unless preamble > + (setq preamble "")) > + (let ((todo (lambda () One space. > +;;;; Data tables > +;; > + > +(ert-deftest ob-haskell/int-table-data () > + "From worg: int-table-data." > + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" > + "sum [sum r | r <- t]" > + "#+name: int-table-data > + | 1 | 2 | > + | 3 | 4 |")))) > + > +(ert-deftest ob-haskell/float-table-data () > + "From worg: float-table-data." > + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" > + "sum [sum r | r <- t]" > + "#+name: float-table-data > + | 1.1 | 2.2 | > + | 3.3 | 4.4 |")))) > + > +(ert-deftest ob-haskell/string-table-data () > + "From worg: string-table-data." > + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" > + "concat [concat r | r <- t]" > + "#+name: string-table-data > + | a | b | > + | c | d |")))) > + > +;;;; Reuse results > +;; > +(ert-deftest ob-haskell/reuse-table () > + "Reusing a computed tables." > + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" > + "sum [sum r | r <- t]" > + "#+name: a-table > +#+begin_src haskell > + [ [x..x+2] | x <- [1,4 .. 12] ] > +#+end_src > +")))) > + > + > +;;;; Not define errors > +;; Single space? -- Best, RY ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-05-07 9:18 ` Ruijie Yu via General discussions about Org-mode. @ 2023-05-07 11:15 ` Bruno Barbier 0 siblings, 0 replies; 26+ messages in thread From: Bruno Barbier @ 2023-05-07 11:15 UTC (permalink / raw) To: Ruijie Yu; +Cc: Ihor Radchenko, emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 1963 bytes --] Ruijie Yu <ruijie@netyu.xyz> writes: > Minor remarks below regarding the patchset. > > Bruno Barbier <brubar.cs@gmail.com> writes: > >> +;; Copyright (c) 2023 Free Software Foundation, Inc. > > lisp/org.el has only a single space, so probably single space here as well. Done. >> + >> +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> >> + >> +;; This program is free software; you can redistribute it and/or modify >> +;; it under the terms of the GNU General Public License as published by >> +;; the Free Software Foundation, either version 3 of the License, or >> +;; (at your option) any later version. > > Do we need the text for "part of GNU Emacs"? > I guess it doesn't harm: I added it, thanks. >> + >> +(defun test-ob-haskell-ghci--with-global-session-worker (todo) >> + "See `test-ob-haskell-ghci--with-global-session-worker'." > > This docstring doesn't say much and only refers to itself. Maybe > explain what it does? (Or now that I look at it, potentially you wanted > to refer to the macro `test-ob-haskell-ghci-with-global-session' > instead.) I've rewritten that function later ... which made the documentation even worse :-) I've fixed it, thanks. > >> +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) >> + "Execute the code block CONTENT in a new GHCi session; return the result. >> +Add ARGS to the code block argument line. Insert PREAMBLE >> +before the code block. When UNPROTECTED is non-nil, don't control >> +which session is used (i.e. don't call >> +`test-ob-haskell-ghci--with-global-session-worker')." >> + (when (listp content) >> + (setq content (string-join content "\n"))) >> + (unless preamble >> + (setq preamble "")) >> + (let ((todo (lambda () > > One space. AFAICS, the last version has only one space here. >> + >> +;;;; Not define errors >> +;; > Single space? It was an invisible 'd' actually; I repainted in black :-) Thanks. Thank you for your review, Bruno [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 15081 bytes --] From 136878a096eb9f459e97da6617f94ba84085db9b Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 01/13] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 454 +++++++++++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..4023873de --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,454 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This file is part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html +;;;; FIXME: Random failures +;; +;; To increase the chances of failure when running tests, you can use this command line: +;; +;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED +;; + +;;;; Status +;; +;; All the tests should succeed (except for random failures); those +;; flagged with ":expected-result :failed" are known +;; limitations/bugs. Tested with (2023-03-18): +;; +;; | emacs-version | 29.0.60 | +;; | org-version | main@4cad6c8ea (Mar 16 2023) | +;; | haskell-mode | master@20d4e23 (Mar 4 2023) | +;; | ghci | 9.0.2 | + + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci--with-global-session-worker (todo) + "See `test-ob-haskell-ghci--with-global-session-worker'." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (unwind-protect (funcall todo) + ;; Kill the "*haskell*" buffer to not pollute other tests. + (when-let ((hb (get-buffer "*haskell*"))) + (with-current-buffer hb + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer hb)))))) + +(defmacro test-ob-haskell-ghci-with-global-session (&rest body) + "Eval BODY in a new session, then destroy the session. +The library ob-haskell doesn't implement session yet. It will +always use a buffer named \"*haskell*\". We kill that buffer +after the source block execution. To be safe, we fail if such a +buffer already exists." + `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, don't control +which session is used (i.e. don't call +`test-ob-haskell-ghci--with-global-session-worker')." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-with-global-session (funcall todo))))) + + +;;; Tests + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Tuples +;; + +(ert-deftest ob-haskell/a-simple-tuple () + "Evaluation of tuple of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "(1,2,3)")))) + + +(ert-deftest ob-haskell/2D-tuples () + "Evaluation of nested tuples into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "((1,2,3), (4,5,6))")))) + +(ert-deftest ob-haskell/2D-tuples-multilines () + "Evaluation of nested tuples into a table, as multilines." + (should (equal '((1 2 3) (4 5 6) (7 8 9)) + (test-ob-haskell-ghci "" " +:{ +( (1,2,3) +, (4,5,6) +, (7,8,9) +) +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + +;;;; Reuse results +;; +(ert-deftest ob-haskell/reuse-table () + "Reusing a computed tables." + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" + "sum [sum r | r <- t]" + "#+name: a-table +#+begin_src haskell + [ [x..x+2] | x <- [1,4 .. 12] ] +#+end_src +")))) + + +;;;; Not defined errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + (let ((r (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + (should (and r (string-match "Variable not in scope" r))))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1294 bytes --] From 21bfe4a1c932b1cb3a40c8df21e08c1907f08b31 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 02/13] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 909de19ab..500be89a2 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,14 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.39.3 [-- Attachment #4: 0003-testing-lisp-test-ob-haskell-ghci.el-Fix-some-tests.patch --] [-- Type: text/x-patch, Size: 1510 bytes --] From 040c505ede7b207b8c847660471d53233a956531 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:25:19 +0100 Subject: [PATCH 03/13] * testing/lisp/test-ob-haskell-ghci.el: Fix some tests (ob-haskell/2D-lists-multilines): (ob-haskell/ghci-info): Fix incorrect test assertions. --- testing/lisp/test-ob-haskell-ghci.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 4023873de..1a060a412 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -263,8 +263,7 @@ (ert-deftest ob-haskell/2D-lists () (ert-deftest ob-haskell/2D-lists-multilines () "Evaluation of nested lists into a table, as multilines." - :expected-result :failed - (should (equal '((1 2 3) (4 5 6)) + (should (equal '((1 2 3) (4 5 6) (7 8 9)) (test-ob-haskell-ghci "" " :{ [ [1..3] @@ -445,8 +444,9 @@ (ert-deftest ob-haskell/ghci-type () (ert-deftest ob-haskell/ghci-info () "The ghci meta command ':info' ." - (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" - (test-ob-haskell-ghci ":results output" ":info repeat")))) + (should (string-match-p + "repeat :: a -> \\[a\\][ \t]+-- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) (provide 'test-ob-haskell-ghci) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2126 bytes --] From d37a3db8eb5ff1f8cdeb7625bff7b07e2e5bfe83 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 04/13] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 1a060a412..0a5e83280 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -119,7 +119,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -189,7 +188,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -201,7 +199,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -236,7 +233,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -360,7 +356,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #6: 0005-lisp-ob-haskell-Request-the-last-value-from-GHCi.patch --] [-- Type: text/x-patch, Size: 4161 bytes --] From 6857bce319ea181ddf40e0b1dadd16c35badbbe4 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 09:59:31 +0100 Subject: [PATCH 05/13] lisp/ob-haskell: Request the last value from GHCi * lisp/ob-haskell.el (org-babel-interpret-haskell): When the result type is 'value, use the last value as defined by GHCi. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to output/value. --- lisp/ob-haskell.el | 32 ++++++++++++++++++++++------ testing/lisp/test-ob-haskell-ghci.el | 6 ++---- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 500be89a2..961ae9c8a 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -135,12 +135,32 @@ (defun org-babel-interpret-haskell (body params) (session (org-babel-haskell-initiate-session session params)) (comint-preoutput-filter-functions (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert org-babel-haskell-eoe) - (comint-send-input nil t))) + (raw (pcase result-type + (`output + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert (org-trim full-body)) + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t))) + (`value (org-babel-comint-with-output + (session org-babel-haskell-eoe nil full-body) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (comint-send-input nil t) + (insert full-body) + (comint-send-input nil t) + (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil) + (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") + (comint-send-input nil t) + (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (comint-send-input nil t)) + ) + )) (results (mapcar #'org-strip-quotes (cdr (member org-babel-haskell-eoe (reverse (mapcar #'org-trim raw))))))) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 0a5e83280..eefa26042 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -114,8 +114,8 @@ (ert-deftest ob-haskell/hello-world-output () (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-nothing () - :expected-result :failed - (should (equal "" + ;; GHCi prints the value on standard output. So, the last value is part of the output. + (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () @@ -417,12 +417,10 @@ (ert-deftest ob-haskell/results-value-2 () (ert-deftest ob-haskell/results-value-3 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) (ert-deftest ob-haskell/results-value-4 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" " putStrLn \"3\" return () -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #7: 0006-ob-haskell-Implement-sessions.patch --] [-- Type: text/x-patch, Size: 4757 bytes --] From f8a7978467551057439f0baa2342f4a6945df43a Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:06:44 +0100 Subject: [PATCH 06/13] ob-haskell: Implement sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Implement sessions. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to sessions. --- lisp/ob-haskell.el | 50 +++++++++++++++++++++------- testing/lisp/test-ob-haskell-ghci.el | 8 +++-- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 961ae9c8a..6bbc91439 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -51,6 +51,8 @@ (declare-function haskell-mode "ext:haskell-mode" ()) (declare-function run-haskell "ext:inf-haskell" (&optional arg)) (declare-function inferior-haskell-load-file "ext:inf-haskell" (&optional reload)) +(declare-function inferior-haskell-start-process + "ext:inf-haskell" ()) (declare-function org-entry-get "org" (pom property &optional inherit literal-nil)) (defvar org-babel-tangle-lang-exts) @@ -183,20 +185,44 @@ (defun org-babel-execute:haskell (body params) (org-babel-interpret-haskell body params) (org-babel-haskell-execute body params)))) -(defun org-babel-haskell-initiate-session (&optional _session _params) + + + +;; Variable defined in inf-haskell (haskell-mode package). +(defvar inferior-haskell-buffer) + +(defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -If there is not a current inferior-process-buffer in SESSION -then create one. Return the initialized session." +Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") - (or (get-buffer "*haskell*") - (save-window-excursion - (run-haskell) - (sleep-for 0.25) - ;; Disable secondary prompt. - (org-babel-comint-input-command - (current-buffer) - ":set prompt-cont \"\"") - (current-buffer)))) + (when (and session-name (string= session-name "none")) + (setq session-name nil)) + (unless session-name + ;; As haskell-mode is using the buffer name "*haskell*", we stay + ;; away from it. + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) + (let ((session (get-buffer session-name))) + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (let ((inferior-haskell-buffer session)) + (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) + (with-current-buffer session (rename-buffer "*haskell*"))) + (save-window-excursion + ;; We don't use `run-haskell' to not popup the buffer. + ;; And we protect default-directory. + (let ((default-directory default-directory)) + (inferior-haskell-start-process)) + (sleep-for 0.25) + (setq session inferior-haskell-buffer) + (with-current-buffer session (rename-buffer session-name)) + ;; Disable secondary prompt. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session)))) + session)) + (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index eefa26042..0a7e5738c 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -134,7 +134,6 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) @@ -144,11 +143,16 @@ (ert-deftest ob-haskell/sessions-must-not-share-variables () (ert-deftest ob-haskell/no-session-means-one-shot-sessions () "When no session, use a new session." - :expected-result :failed (test-ob-haskell-ghci-with-global-session (test-ob-haskell-ghci "" "x=2" nil :unprotected) (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) +(ert-deftest ob-haskell/reuse-variables-in-same-session () + "Reuse variables between blocks using the same session." + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) + + ;;;; Values ;; -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #8: 0007-ob-haskell-Update-tests-about-errors.patch --] [-- Type: text/x-patch, Size: 1491 bytes --] From b8c8038f79247b9ab1643c4766c2c85ff2a2f6d8 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:09:26 +0100 Subject: [PATCH 07/13] ob-haskell: Update tests about errors testing/lisp/test-ob-haskell-ghci.el: Update tests about errors. --- testing/lisp/test-ob-haskell-ghci.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 0a7e5738c..089042553 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -345,18 +345,20 @@ (ert-deftest ob-haskell/reuse-table () (ert-deftest ob-haskell/not-defined () "Evaluation of undefined variables." - (should (string-match "Variable not in scope" - (test-ob-haskell-ghci "" "notDefined :: IO Int")))) + :expected-result :failed + (should-error (test-ob-haskell-ghci "" "notDefined :: IO Int"))) + (ert-deftest ob-haskell/not-defined-then-defined-1 () "Evaluation of undefined variables. This is a valid haskell source, but, invalid when entered one line at a time in GHCi." - (let ((r (test-ob-haskell-ghci "" " + :expected-result :failed + (should-error (test-ob-haskell-ghci "" " v :: Int v = 4 "))) - (should (and r (string-match "Variable not in scope" r))))) + (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #9: 0008-testing-lisp-test-ob-haskell-ghci.el-Cleanup-comment.patch --] [-- Type: text/x-patch, Size: 1495 bytes --] From ad1ce32a7d281a842ba3b790b79099254b619fff Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 1 Apr 2023 10:00:30 +0200 Subject: [PATCH 08/13] * testing/lisp/test-ob-haskell-ghci.el: Cleanup comments --- testing/lisp/test-ob-haskell-ghci.el | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 089042553..2bcff5ee0 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -25,24 +25,6 @@ ;; ;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html ;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html -;;;; FIXME: Random failures -;; -;; To increase the chances of failure when running tests, you can use this command line: -;; -;; (for I in 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10 0 1 2 3 4 5 6 7 8 9 10; do make 'BTEST_OB_LANGUAGES=haskell' BTEST_RE='haskell' test-dirty & done) 2>&1 | grep FAILED -;; - -;;;; Status -;; -;; All the tests should succeed (except for random failures); those -;; flagged with ":expected-result :failed" are known -;; limitations/bugs. Tested with (2023-03-18): -;; -;; | emacs-version | 29.0.60 | -;; | org-version | main@4cad6c8ea (Mar 16 2023) | -;; | haskell-mode | master@20d4e23 (Mar 4 2023) | -;; | ghci | 9.0.2 | - ;;; Code: ;; -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #10: 0009-lisp-ob-haskell.el-Simplify-org-babel-haskell-eoe.patch --] [-- Type: text/x-patch, Size: 2482 bytes --] From 6ca6e108979eced50ae10d1efc0bf0f55d5f6a75 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 1 Apr 2023 10:19:24 +0200 Subject: [PATCH 09/13] lisp/ob-haskell.el: Simplify org-babel-haskell-eoe lisp/ob-haskell.el (org-babel-haskell-eoe): New default value. (org-babel-interpret-haskell): Update for the new value of `org-babel-haskell-eoe'. --- lisp/ob-haskell.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 6bbc91439..98b1b10f0 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -63,7 +63,7 @@ (defvar org-babel-default-header-args:haskell (defvar org-babel-haskell-lhs2tex-command "lhs2tex") -(defvar org-babel-haskell-eoe "\"org-babel-haskell-eoe\"") +(defvar org-babel-haskell-eoe "org-babel-haskell-eoe") (defvar haskell-prompt-regexp) @@ -143,7 +143,7 @@ (defun org-babel-interpret-haskell (body params) (session org-babel-haskell-eoe nil full-body) (insert (org-trim full-body)) (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t))) (`value (org-babel-comint-with-output (session org-babel-haskell-eoe nil full-body) @@ -153,13 +153,13 @@ (defun org-babel-interpret-haskell (body params) (comint-send-input nil t) (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t)) (org-babel-comint-with-output (session org-babel-haskell-eoe nil) (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") (comint-send-input nil t) - (insert (concat "putStrLn (\"\\\"\" ++ " org-babel-haskell-eoe " ++ \"\\\"\")\n")) + (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) (comint-send-input nil t)) ) )) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #11: 0010-testing-lisp-test-ob-haskell-ghci.el-Test-output-wit.patch --] [-- Type: text/x-patch, Size: 1817 bytes --] From c4adca4caa3d6d10ba7b65c4de7c132aa56871d0 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:27:57 +0200 Subject: [PATCH 10/13] * testing/lisp/test-ob-haskell-ghci.el: Test output without EOL (ob-haskell/output-without-eol-1): (ob-haskell/output-without-eol-2): (ob-haskell/output-without-eol-3): New tests. --- testing/lisp/test-ob-haskell-ghci.el | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 2bcff5ee0..36f745e61 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -166,6 +166,38 @@ (ert-deftest ob-haskell/eval-strings () "Evaluation of strings." (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) +;;;; Output without EOL +;; + +(ert-deftest ob-haskell/output-without-eol-1 () + "Cannot get output from incomplete lines, when entered line by line." + :expected-result :failed + (should (equal "123" + (test-ob-haskell-ghci ":results output" " + putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +")))) + +(ert-deftest ob-haskell/output-without-eol-2 () + "Incomplete output lines are OK when using a multiline block." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +:{ + do putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +:} +")))) + +(ert-deftest ob-haskell/output-without-eol-3 () + "Incomplete output lines are OK on one line." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +do { putStr(\"1\"); putStr(\"2\"); putStr(\"3\"); putStr(\"\\n\") } +")))) ;;;; Local variables (ert-deftest ob-haskell/let-one-line () -- 2.39.3 [-- Attachment #12: 0011-lisp-ob-haskell.el-Fix-how-to-use-sessions.patch --] [-- Type: text/x-patch, Size: 11477 bytes --] From 2bae53acd280b394c8f1624aef2426bbe6720f03 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:43:16 +0200 Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Redesign how to handle session names. (org-babel-haskell-with-session): New function to manage sessions. (org-babel-interpret-haskell): Refactor code. Use `org-babel-haskell-with-session` to manage sessions. (org-babel-prep-session:haskell): Don't ignore the PARAMS argument. --- lisp/ob-haskell.el | 182 +++++++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 72 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 98b1b10f0..deaa434f8 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -129,54 +129,58 @@ (defun org-babel-interpret-haskell (body params) (lambda () (setq-local comint-prompt-regexp (concat haskell-prompt-regexp "\\|^λ?> ")))) - (let* ((session (cdr (assq :session params))) - (result-type (cdr (assq :result-type params))) - (full-body (org-babel-expand-body:generic - body params - (org-babel-variable-assignments:haskell params))) - (session (org-babel-haskell-initiate-session session params)) - (comint-preoutput-filter-functions - (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (pcase result-type - (`output - (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t))) - (`value (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") - (comint-send-input nil t) - (insert full-body) - (comint-send-input nil t) - (insert "__LAST_VALUE_IMPROBABLE_NAME__=it\n") - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t)) - (org-babel-comint-with-output - (session org-babel-haskell-eoe nil) - (insert "__LAST_VALUE_IMPROBABLE_NAME__\n") - (comint-send-input nil t) - (insert (concat "putStrLn \"" org-babel-haskell-eoe "\"\n")) - (comint-send-input nil t)) - ) - )) - (results (mapcar #'org-strip-quotes - (cdr (member org-babel-haskell-eoe - (reverse (mapcar #'org-trim raw))))))) - (org-babel-reassemble-table - (let ((result - (pcase result-type - (`output (mapconcat #'identity (reverse results) "\n")) - (`value (car results))))) - (org-babel-result-cond (cdr (assq :result-params params)) - result (when result (org-babel-script-escape result)))) - (org-babel-pick-name (cdr (assq :colname-names params)) - (cdr (assq :colname-names params))) - (org-babel-pick-name (cdr (assq :rowname-names params)) - (cdr (assq :rowname-names params)))))) + (org-babel-haskell-with-session + params + (lambda (session) + (cl-labels + ((csend (txt) + (insert txt) (comint-send-input nil t)) + (eom () + (csend (concat "putStrLn \"" org-babel-haskell-eoe "\"\n"))) + (with-output (todo) + (let ((comint-preoutput-filter-functions + (cons 'ansi-color-filter-apply + comint-preoutput-filter-functions))) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil nil) + (funcall todo))))) + (let* ((result-type (cdr (assq :result-type params))) + (full-body (org-babel-expand-body:generic + body params + (org-babel-variable-assignments:haskell params))) + (raw (pcase result-type + (`output + (with-output + (lambda () (csend (org-trim full-body)) (eom)))) + (`value + ;; We first compute the value and store the + ;; value, ignoring any output. + (with-output + (lambda () + (csend "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (csend (org-trim full-body)) + (csend "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (eom))) + ;; We now display and capture the value. + (with-output + (lambda() + (csend "__LAST_VALUE_IMPROBABLE_NAME__\n") + (eom)))))) + (results (mapcar #'org-strip-quotes + (cdr (member org-babel-haskell-eoe + (reverse (mapcar #'org-trim raw))))))) + (org-babel-reassemble-table + (let ((result + (pcase result-type + (`output (mapconcat #'identity (reverse results) "\n")) + (`value (car results))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (when result (org-babel-script-escape result)))) + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colname-names params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rowname-names params))))))))) + (defun org-babel-execute:haskell (body params) "Execute a block of Haskell code." @@ -186,6 +190,23 @@ (defun org-babel-execute:haskell (body params) (org-babel-haskell-execute body params)))) +(defun org-babel-haskell-with-session (params todo) + "Call TODO with a suitable session buffer. +Use PARAMS to get/create/destroy the session as needed. +Return the result of the call." + (let* ((sn (cdr (assq :session params))) + (session (org-babel-haskell-initiate-session sn params)) + (one-shot (equal sn "none"))) + (unwind-protect + (funcall todo session) + (when (and one-shot (buffer-live-p session)) + ;; As we don't control how the session temporary buffer is + ;; created, we need to explicitly work around the hooks and + ;; query functions. + (with-current-buffer session + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer session))))))) ;; Variable defined in inf-haskell (haskell-mode package). @@ -193,34 +214,51 @@ (defvar inferior-haskell-buffer) (defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -Return the initialized session." +Return the initialized session, i.e. the buffer for this session. +When SESSION-NAME is nil, use a global session named +\"*ob-haskell*\". When SESSION-NAME is the string \"none\", use +a temporary buffer. Else, (re)use the session named +SESSION-NAME. The buffer name is the session name. See also +`org-babel-haskell-with-session'." (org-require-package 'inf-haskell "haskell-mode") - (when (and session-name (string= session-name "none")) - (setq session-name nil)) - (unless session-name - ;; As haskell-mode is using the buffer name "*haskell*", we stay - ;; away from it. - (setq session-name (generate-new-buffer-name "*ob-haskell*"))) + (cond + ((equal "none" session-name) + ;; Temporary buffer name. + (setq session-name (generate-new-buffer-name " *ob-haskell-tmp*"))) + ((eq nil session-name) + ;; The global default session. As haskell-mode is using the buffer + ;; named "*haskell*", we stay away from it. + (setq session-name "*ob-haskell*"))) (let ((session (get-buffer session-name))) (save-window-excursion (or (org-babel-comint-buffer-livep session) (let ((inferior-haskell-buffer session)) - (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) - (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) - (with-current-buffer session (rename-buffer "*haskell*"))) - (save-window-excursion - ;; We don't use `run-haskell' to not popup the buffer. - ;; And we protect default-directory. - (let ((default-directory default-directory)) - (inferior-haskell-start-process)) - (sleep-for 0.25) - (setq session inferior-haskell-buffer) - (with-current-buffer session (rename-buffer session-name)) - ;; Disable secondary prompt. - (org-babel-comint-input-command - session - ":set prompt-cont \"\"") - session)))) + ;; As inferior-haskell expects the buffer to be named + ;; "*haskell*", we rename it, unless the user explicitly + ;; requested to use the name "*haskell*". + (when (not (equal "*haskell*" session-name)) + (when (and (bufferp session) + (not (org-babel-comint-buffer-livep session))) + (when (bufferp "*haskell*") + (user-error "Conflicting buffer '*haskell*', rename it or kill it")) + (with-current-buffer session (rename-buffer "*haskell*")))) + (unwind-protect + (save-window-excursion + ;; We don't use `run-haskell' to not popup the buffer. + ;; And we protect default-directory. + (let ((default-directory default-directory)) + (inferior-haskell-start-process)) + (sleep-for 0.25) + (setq session inferior-haskell-buffer)) + (when (and (not (equal "*haskell*" session-name)) + (bufferp session)) + (with-current-buffer session (rename-buffer session-name)))) + ;; Disable secondary prompt. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session) + )) session)) @@ -237,7 +275,7 @@ (defun org-babel-load-session:haskell (session body params) (defun org-babel-prep-session:haskell (session params) "Prepare SESSION according to the header arguments in PARAMS." (save-window-excursion - (let ((buffer (org-babel-haskell-initiate-session session))) + (let ((buffer (org-babel-haskell-initiate-session session params))) (org-babel-comint-in-buffer buffer (mapc (lambda (line) (insert line) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #13: 0012-testing-lisp-test-ob-haskell-ghci.el-Modify-test-ob-.patch --] [-- Type: text/x-patch, Size: 3515 bytes --] From 61b7727d950bf884c13110f39c6b17eba0237b32 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 11:06:45 +0200 Subject: [PATCH 12/13] * testing/lisp/test-ob-haskell-ghci.el: Modify `test-ob-haskell-ghci` * testing/lisp/test-ob-haskell-ghci.el (test-ob-haskell-ghci--with-global-session-worker) (test-ob-haskell-ghci-with-global-session): Deleted. (test-ob-haskell-ghci-checking-buffers): New function. (test-ob-haskell-ghci): Update to handle the new meaning of sessions. --- testing/lisp/test-ob-haskell-ghci.el | 42 +++++++++++----------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 36f745e61..fcce06365 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -38,43 +38,33 @@ (unless (featurep 'haskell-mode) ;;; Helpers ;; -(defun test-ob-haskell-ghci--with-global-session-worker (todo) - "See `test-ob-haskell-ghci--with-global-session-worker'." +(defun test-ob-haskell-ghci-checking-buffers (todo) + "Check some buffer related invariants.." (when (get-buffer "*haskell*") (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) - (unwind-protect (funcall todo) - ;; Kill the "*haskell*" buffer to not pollute other tests. + (prog1 (funcall todo) (when-let ((hb (get-buffer "*haskell*"))) - (with-current-buffer hb - (let ((kill-buffer-query-functions nil) - (kill-buffer-hook nil)) - (kill-buffer hb)))))) - -(defmacro test-ob-haskell-ghci-with-global-session (&rest body) - "Eval BODY in a new session, then destroy the session. -The library ob-haskell doesn't implement session yet. It will -always use a buffer named \"*haskell*\". We kill that buffer -after the source block execution. To be safe, we fail if such a -buffer already exists." - `(test-ob-haskell-ghci--with-global-session-worker (lambda () ,@body))) + ;; We created a "*haskell*" buffer. That shouldn't happen. + (error "'ob-haskell' created a buffer named '*haskell*'")))) + + (defun test-ob-haskell-ghci (args content &optional preamble unprotected) "Execute the code block CONTENT in a new GHCi session; return the result. Add ARGS to the code block argument line. Insert PREAMBLE -before the code block. When UNPROTECTED is non-nil, don't control -which session is used (i.e. don't call -`test-ob-haskell-ghci--with-global-session-worker')." +before the code block. When UNPROTECTED is non-nil, check pre/post conditions." (when (listp content) (setq content (string-join content "\n"))) (unless preamble (setq preamble "")) - (let ((todo (lambda () - (org-test-with-temp-text - (concat preamble "\n" "#+begin_src haskell :compile no " - args "\n" "<point>" content "\n#+end_src") - (org-babel-execute-src-block))))) - (if unprotected (funcall todo) - (test-ob-haskell-ghci-with-global-session (funcall todo))))) + (let ((todo (lambda () + (prog1 (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block)))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-checking-buffers todo)))) + ;;; Tests -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #14: 0013-testing-lisp-test-ob-haskell-ghci.el-Update-session-.patch --] [-- Type: text/x-patch, Size: 3132 bytes --] From 52b7b78e3413e93cb8098e699a0b44c04e94bb37 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 11:10:42 +0200 Subject: [PATCH 13/13] * testing/lisp/test-ob-haskell-ghci.el: Update session tests * testing/lisp/test-ob-haskell-ghci.el (ob-haskell/no-session-means-one-shot-sessions): Deleted. (ob-haskell/session-named-none-means-one-shot-sessions): New test. (ob-haskell/sessions-must-not-share-variables): Rewrite tests to match the new meaning of session names. (ob-haskell/may-use-the-*haskell*-session): New test. --- testing/lisp/test-ob-haskell-ghci.el | 36 +++++++++++++++++++--------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index fcce06365..f49ebcc40 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -106,24 +106,38 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) - (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) - (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - )) - -(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil))) + (test-ob-haskell-ghci ":session s2" "x=3" nil) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil))) + ) + +(ert-deftest ob-haskell/session-named-none-means-one-shot-sessions () "When no session, use a new session." - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci "" "x=2" nil :unprotected) - (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil)))) (ert-deftest ob-haskell/reuse-variables-in-same-session () "Reuse variables between blocks using the same session." (test-ob-haskell-ghci ":session s1" "x=2" nil) (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) +(ert-deftest ob-haskell/may-use-the-*haskell*-session () + "The user may use the special *haskell* buffer." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't run this test")) + (unwind-protect + (progn + (test-ob-haskell-ghci ":session *haskell*" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session *haskell*" "x" nil :unprotected)))) + (with-current-buffer "*haskell*" + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer "*haskell*"))))) + + ;;;; Values -- 2.39.3 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-05-07 8:50 ` Bruno Barbier 2023-05-07 9:18 ` Ruijie Yu via General discussions about Org-mode. @ 2023-05-08 10:59 ` Ihor Radchenko 2023-05-21 7:40 ` Bruno Barbier 1 sibling, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-05-08 10:59 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: > Let me know if you see further improvement before pushing this. Thanks for the update! I can see that you limited the tests scope to :session blocks. Would it be possible to extend the existing tests to :compile yes case? From a glance, it does not look like you need to change much - Haskell behaviour should be similar with or without ghci. > From 9972b926f55cb970e0b520f8726a3684118017b6 Mon Sep 17 00:00:00 2001 > From: Ihor Radchenko <yantar92@posteo.net> > Date: Fri, 24 Mar 2023 11:20:22 +0100 > Subject: [PATCH 02/13] org-babel-haskell-initiate-session: Remove secondary > prompt > > * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set > secondary prompt to "". If we do not do this, org-comint may treat > secondary prompts as a part of output. > + (sleep-for 0.25) > + ;; Disable secondary prompt. It would be useful to explain the purpose of disabling the secondary prompt in the source code comment itself, not just in the commit message. It will improve readability. > From 352d18399961fedc45cc2d64007016426e1ecd40 Mon Sep 17 00:00:00 2001 > From: Ihor Radchenko <yantar92@posteo.net> > Date: Fri, 24 Mar 2023 11:26:00 +0100 > Subject: [PATCH 04/13] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed I do not see PATCH 03/13 in the attachments. > From 7d66cff5cc23bb786cb2843f4326d2869512ccac Mon Sep 17 00:00:00 2001 > From: Bruno BARBIER <brubar.cs@gmail.com> > Date: Sat, 25 Mar 2023 10:06:44 +0100 > Subject: [PATCH 06/13] ob-haskell: Implement sessions > > + (unless session-name > + ;; As haskell-mode is using the buffer name "*haskell*", we stay > + ;; away from it. > + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) > + (let ((session (get-buffer session-name))) session is not a buffer or nil, if no buffer named session-name exists. > + (save-window-excursion > + (or (org-babel-comint-buffer-livep session) Below, (org-babel-comint-buffer-livep session) is nil, which implies either that session is nil, does not exist, not live, or does not have a process attached. > + (let ((inferior-haskell-buffer session)) > + (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) (not (org-babel-comint-buffer-livep session)) is always t here. Also, session may be a killed buffer object. It is still a buffer, but not usable. See `buffer-live-p'. > + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) > + (with-current-buffer session (rename-buffer "*haskell*"))) So, you are now renaming the unique session buffer back to "*haskell*". And never rename it back to expected :session <value>. Users might be confused. > + (save-window-excursion > + ;; We don't use `run-haskell' to not popup the buffer. > + ;; And we protect default-directory. > + (let ((default-directory default-directory)) > + (inferior-haskell-start-process)) This is a workaround for a nasty side effect of running `inferior-haskell-start-process'. We should report this to haskell-mode developers, leaving appropriate comment in the code. > + (sleep-for 0.25) > + (setq session inferior-haskell-buffer) > + (with-current-buffer session (rename-buffer session-name)) This generally looks like a brittle workaround for inner workings of haskell-mode. I recommend sending an email to haskell-mode devs, requesting multiple session support. Otherwise, this whole code eventually be broken. > Subject: [PATCH 10/13] * testing/lisp/test-ob-haskell-ghci.el: Test output > without EOL > ... > +(ert-deftest ob-haskell/output-without-eol-1 () > + "Cannot get output from incomplete lines, when entered line by line." > + :expected-result :failed > + (should (equal "123" > + (test-ob-haskell-ghci ":results output" " > + putStr(\"1\") > + putStr(\"2\") > + putStr(\"3\") > + putStr(\"\\n\") > +")))) May you explain more about this bug? > Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions > > + (org-babel-haskell-with-session This kind of names are usually dedicated to macro calls. But `org-babel-haskell-with-session' is instead a function. I think a macro will be better. And you will be able to get rid of unnecessary lambda. > + params > + (lambda (session) > + (cl-labels > + ((csend (txt) > + (eom () > + (with-output (todo) When using `cl-labels', please prefer longer, more descriptive function names. These functions do not have a docstring and I now am left guessing and reading the function code repeatedly to understand the usage. > + (full-body (org-babel-expand-body:generic > + body params > + (org-babel-variable-assignments:haskell params))) I think we want `org-babel-expand-src-block' here instead of using semi-internal ob-core.el parts. > - (let ((buffer (org-babel-haskell-initiate-session session))) > + (let ((buffer (org-babel-haskell-initiate-session session params))) PARAMS argument is ignored by `org-babel-haskell-initiate-session'. I am not sure why you are trying to pass it here. > Subject: [PATCH 12/13] * testing/lisp/test-ob-haskell-ghci.el: Modify > `test-ob-haskell-ghci` Here and in some other patches you are undoing changes made in previous patches. May you please consolidate transient changes by squashing commits? It will make further reviews easier. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-05-08 10:59 ` Ihor Radchenko @ 2023-05-21 7:40 ` Bruno Barbier 2023-06-02 8:44 ` Ihor Radchenko 0 siblings, 1 reply; 26+ messages in thread From: Bruno Barbier @ 2023-05-21 7:40 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 9071 bytes --] Hi Ihor, Thanks for the review. Ihor Radchenko <yantar92@posteo.net> writes: > Bruno Barbier <brubar.cs@gmail.com> writes: > I can see that you limited the tests scope to :session blocks. > Would it be possible to extend the existing tests to :compile yes case? > From a glance, it does not look like you need to change much - Haskell > behaviour should be similar with or without ghci. Except for one line expressions, GHCi inputs and haskell modules are two different things. I doubt there will be much to share; that's why I've focused from the start on GHCi only. As I've a lot of other things that I'd like to do to improve my day to day workflow, and as I'm barely using ob-haskell, I can't promise I'll work on this any time soon. >> From 9972b926f55cb970e0b520f8726a3684118017b6 Mon Sep 17 00:00:00 2001 >> From: Ihor Radchenko <yantar92@posteo.net> >> Date: Fri, 24 Mar 2023 11:20:22 +0100 >> Subject: [PATCH 02/13] org-babel-haskell-initiate-session: Remove secondary >> prompt >> >> * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set >> secondary prompt to "". If we do not do this, org-comint may treat >> secondary prompts as a part of output. > >> + (sleep-for 0.25) >> + ;; Disable secondary prompt. > > It would be useful to explain the purpose of disabling the secondary > prompt in the source code comment itself, not just in the commit > message. It will improve readability. Are you reviewing your own improvements ? :-) Fixed. I've copied/pasted your explanation in the code. >> From 352d18399961fedc45cc2d64007016426e1ecd40 Mon Sep 17 00:00:00 2001 >> From: Ihor Radchenko <yantar92@posteo.net> >> Date: Fri, 24 Mar 2023 11:26:00 +0100 >> Subject: [PATCH 04/13] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed > > I do not see PATCH 03/13 in the attachments. Sorry. I forgot it the first time. The email, after handling the comments from Ruijie Yu, had it though. >> From 7d66cff5cc23bb786cb2843f4326d2869512ccac Mon Sep 17 00:00:00 2001 >> From: Bruno BARBIER <brubar.cs@gmail.com> >> Date: Sat, 25 Mar 2023 10:06:44 +0100 >> Subject: [PATCH 06/13] ob-haskell: Implement sessions >> >> + (unless session-name >> + ;; As haskell-mode is using the buffer name "*haskell*", we stay >> + ;; away from it. >> + (setq session-name (generate-new-buffer-name "*ob-haskell*"))) >> + (let ((session (get-buffer session-name))) > > session is not a buffer or nil, if no buffer named session-name exists. The argument SESSION-NAME must be a string or nil (I added a test to make clear that it must be a string). Thus, session will either be nil or a live buffer. >> + (save-window-excursion >> + (or (org-babel-comint-buffer-livep session) > > Below, (org-babel-comint-buffer-livep session) is nil, which implies > either that session is nil, does not exist, not live, or does not have a > process attached. ok. So, in our case, session is either nil, or it's a live buffer without an attached process. > >> + (let ((inferior-haskell-buffer session)) >> + (when (and (bufferp session) (not (org-babel-comint-buffer-livep session))) > > (not (org-babel-comint-buffer-livep session)) is always t here. Right. I removed the test. Thanks. > Also, session may be a killed buffer object. It is still a buffer, but > not usable. See `buffer-live-p'. By construction, if 'session' is a buffer, then, it is a live buffer. > >> + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) >> + (with-current-buffer session (rename-buffer "*haskell*"))) > > So, you are now renaming the unique session buffer back to "*haskell*". > And never rename it back to expected :session <value>. Users might be confused. I do rename it back once inf-haskell has initialized the buffer (after run-haskell in the last version). >> + (save-window-excursion >> + ;; We don't use `run-haskell' to not popup the buffer. >> + ;; And we protect default-directory. >> + (let ((default-directory default-directory)) >> + (inferior-haskell-start-process)) > > This is a workaround for a nasty side effect of running > `inferior-haskell-start-process'. We should report this to haskell-mode > developers, leaving appropriate comment in the code. About 'run-haskell', I reverted my change: we're inside a 'save-window-excursion', which looks like the standard way to get rid of unwanted popups (like ob-shell does). About 'default-directory', I'm not sure. Maybe the side effect is done on purpose in inf-haskell. > >> + (sleep-for 0.25) >> + (setq session inferior-haskell-buffer) >> + (with-current-buffer session (rename-buffer session-name)) > > This generally looks like a brittle workaround for inner workings of > haskell-mode. I recommend sending an email to haskell-mode devs, > requesting multiple session support. Otherwise, this whole code > eventually be broken. Yes. It's a workaround. But it looks reasonably safe to me, as the default buffer name isn't going to change, even if they make it configurable. Plus, 'make-comint' (used by the function 'inferior-haskell-start-process' from the library inf-haskell) would also require to rename the buffer, or to forbid session names that don't start and end with "*", or to use buffer names that don't match the session names. > >> Subject: [PATCH 10/13] * testing/lisp/test-ob-haskell-ghci.el: Test output >> without EOL >> ... >> +(ert-deftest ob-haskell/output-without-eol-1 () >> + "Cannot get output from incomplete lines, when entered line by line." >> + :expected-result :failed >> + (should (equal "123" >> + (test-ob-haskell-ghci ":results output" " >> + putStr(\"1\") >> + putStr(\"2\") >> + putStr(\"3\") >> + putStr(\"\\n\") >> +")))) > > May you explain more about this bug? Sure. The function 'putStr' output the string without a newline on stdout (as opposed to the function putStrLn that does add a newline). So, in GHCi, entering: putStr("4") outputs "4" on stdout, then GHCi outputs the prompt, so we get: 4ghci> In the end, 'org-babel-comint-with-output' gets this 1ghci> 2ghci> 3ghci> ghci> org-babel-haskell-eoe ghci> ghci> and filters out everything as being GHCi prompts and the EOE. I'm not really expecting this to be fixed; I just wanted to record the fact. IMHO, users should use one of the alternatives, that are shown in the tests 'ob-haskell/output-without-eol-2' or 'ob-haskell/output-without-eol-3'. > >> Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions >> >> + (org-babel-haskell-with-session > > This kind of names are usually dedicated to macro calls. But > `org-babel-haskell-with-session' is instead a function. I think a macro > will be better. And you will be able to get rid of unnecessary lambda. That looks kind of complicated just to avoid one lambda in one call. But, as I couldn't find a better name, I've translated it into a macro. > >> + params >> + (lambda (session) >> + (cl-labels >> + ((csend (txt) >> + (eom () >> + (with-output (todo) > > When using `cl-labels', please prefer longer, more descriptive function > names. These functions do not have a docstring and I now am left > guessing and reading the function code repeatedly to understand the > usage. I tried to use more descriptive names. I hope it's easier to read now. >> + (full-body (org-babel-expand-body:generic >> + body params >> + (org-babel-variable-assignments:haskell params))) > > I think we want `org-babel-expand-src-block' here instead of using > semi-internal ob-core.el parts. Are you sure about this ? I didn't modify this part and I didn't see this function used in other backends. I've also checked ob-python and ob-shell: they both use the same code as above. >> - (let ((buffer (org-babel-haskell-initiate-session session))) >> + (let ((buffer (org-babel-haskell-initiate-session session params))) > > PARAMS argument is ignored by `org-babel-haskell-initiate-session'. I am > not sure why you are trying to pass it here. We have the PARAMS, and, org-babel-haskell-initiate-session has a PARAMS arguments. So, at the API level, I think it's better to propagate it than to ignore it. But you're right that, today, the current implementation ignores it. I'm fine with dropping that change if you so prefer. >> Subject: [PATCH 12/13] * testing/lisp/test-ob-haskell-ghci.el: Modify >> `test-ob-haskell-ghci` > > Here and in some other patches you are undoing changes made in previous > patches. May you please consolidate transient changes by squashing > commits? It will make further reviews easier. I wasn't sure what would make the review easier: keep the history of changes or squash my updates. I've now squashed all my updates. I've attached the new version. Thanks again for the review. Bruno. [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 13661 bytes --] From 9218d6cebbabc3a22aba8b7dcf37c4e746f34360 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 1/7] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 428 +++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..330937917 --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,428 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This file is part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci-checking-buffers (todo) + "Check some buffer related invariants.." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (prog1 (funcall todo) + (when-let ((hb (get-buffer "*haskell*"))) + ;; We created a "*haskell*" buffer. That shouldn't happen. + (error "'ob-haskell' created a buffer named '*haskell*'")))) + + + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, check pre/post conditions." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (prog1 (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block)))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-checking-buffers todo)))) + + + +;;; Tests + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Tuples +;; + +(ert-deftest ob-haskell/a-simple-tuple () + "Evaluation of tuple of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "(1,2,3)")))) + + +(ert-deftest ob-haskell/2D-tuples () + "Evaluation of nested tuples into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "((1,2,3), (4,5,6))")))) + +(ert-deftest ob-haskell/2D-tuples-multilines () + "Evaluation of nested tuples into a table, as multilines." + (should (equal '((1 2 3) (4 5 6) (7 8 9)) + (test-ob-haskell-ghci "" " +:{ +( (1,2,3) +, (4,5,6) +, (7,8,9) +) +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + +;;;; Reuse results +;; +(ert-deftest ob-haskell/reuse-table () + "Reusing a computed tables." + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" + "sum [sum r | r <- t]" + "#+name: a-table +#+begin_src haskell + [ [x..x+2] | x <- [1,4 .. 12] ] +#+end_src +")))) + + +;;;; Not defined errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + :expected-result :failed + (should-error (test-ob-haskell-ghci "" "notDefined :: IO Int"))) + + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + :expected-result :failed + (should-error (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "3 :: Num p => p" + (test-ob-haskell-ghci ":results output" ":type 3")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1402 bytes --] From ee6c0869f9995de8690871b92b975b0750f03543 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 2/7] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 909de19ab..8f9ae397a 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,16 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt: If we do not do this, + ;; org-comint may treat secondary prompts as a part of + ;; output. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.39.3 [-- Attachment #4: 0003-testing-lisp-test-ob-haskell-ghci.el-Fix-some-tests.patch --] [-- Type: text/x-patch, Size: 1508 bytes --] From 7ff5390fedd1717f9c16f04ac997490d050a9081 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:25:19 +0100 Subject: [PATCH 3/7] * testing/lisp/test-ob-haskell-ghci.el: Fix some tests (ob-haskell/2D-lists-multilines): (ob-haskell/ghci-info): Fix incorrect test assertions. --- testing/lisp/test-ob-haskell-ghci.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 330937917..cf59edb25 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -235,8 +235,7 @@ (ert-deftest ob-haskell/2D-lists () (ert-deftest ob-haskell/2D-lists-multilines () "Evaluation of nested lists into a table, as multilines." - :expected-result :failed - (should (equal '((1 2 3) (4 5 6)) + (should (equal '((1 2 3) (4 5 6) (7 8 9)) (test-ob-haskell-ghci "" " :{ [ [1..3] @@ -419,8 +418,9 @@ (ert-deftest ob-haskell/ghci-type () (ert-deftest ob-haskell/ghci-info () "The ghci meta command ':info' ." - (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" - (test-ob-haskell-ghci ":results output" ":info repeat")))) + (should (string-match-p + "repeat :: a -> \\[a\\][ \t]+-- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) (provide 'test-ob-haskell-ghci) -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2122 bytes --] From 1f7bbe337ec69bfa02ff4c117e70685f288d01e6 Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 4/7] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index cf59edb25..868a56e63 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -91,7 +91,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -161,7 +160,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -173,7 +171,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -208,7 +205,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -334,7 +330,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.39.3 [-- Attachment #6: 0005-lisp-ob-haskell-Request-the-last-value-from-GHCi.patch --] [-- Type: text/x-patch, Size: 6772 bytes --] From d26447d0a0316058a3eb8cb5ea597f4f7ee24e14 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 09:59:31 +0100 Subject: [PATCH 5/7] lisp/ob-haskell: Request the last value from GHCi * lisp/ob-haskell.el (org-babel-interpret-haskell): When the result type is 'value, use the last value as defined by GHCi. (org-babel-haskell-eoe): New default value. (org-babel-interpret-haskell): Update for the new value of `org-babel-haskell-eoe'. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to output/value. --- lisp/ob-haskell.el | 80 ++++++++++++++++++---------- testing/lisp/test-ob-haskell-ghci.el | 6 +-- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 8f9ae397a..6cec21217 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -61,7 +61,7 @@ (defvar org-babel-default-header-args:haskell (defvar org-babel-haskell-lhs2tex-command "lhs2tex") -(defvar org-babel-haskell-eoe "\"org-babel-haskell-eoe\"") +(defvar org-babel-haskell-eoe "org-babel-haskell-eoe") (defvar haskell-prompt-regexp) @@ -127,34 +127,56 @@ (defun org-babel-interpret-haskell (body params) (lambda () (setq-local comint-prompt-regexp (concat haskell-prompt-regexp "\\|^λ?> ")))) - (let* ((session (cdr (assq :session params))) - (result-type (cdr (assq :result-type params))) - (full-body (org-babel-expand-body:generic - body params - (org-babel-variable-assignments:haskell params))) - (session (org-babel-haskell-initiate-session session params)) - (comint-preoutput-filter-functions - (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert org-babel-haskell-eoe) - (comint-send-input nil t))) - (results (mapcar #'org-strip-quotes - (cdr (member org-babel-haskell-eoe - (reverse (mapcar #'org-trim raw))))))) - (org-babel-reassemble-table - (let ((result - (pcase result-type - (`output (mapconcat #'identity (reverse results) "\n")) - (`value (car results))))) - (org-babel-result-cond (cdr (assq :result-params params)) - result (when result (org-babel-script-escape result)))) - (org-babel-pick-name (cdr (assq :colname-names params)) - (cdr (assq :colname-names params))) - (org-babel-pick-name (cdr (assq :rowname-names params)) - (cdr (assq :rowname-names params)))))) + (org-babel-haskell-with-session session params + (cl-labels + ((send-txt-to-ghci (txt) + (insert txt) (comint-send-input nil t)) + (send-eoe () + (send-txt-to-ghci (concat "putStrLn \"" org-babel-haskell-eoe "\"\n"))) + (comint-with-output (todo) + (let ((comint-preoutput-filter-functions + (cons 'ansi-color-filter-apply + comint-preoutput-filter-functions))) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil nil) + (funcall todo))))) + (let* ((result-type (cdr (assq :result-type params))) + (full-body (org-babel-expand-body:generic + body params + (org-babel-variable-assignments:haskell params))) + (raw (pcase result-type + (`output + (comint-with-output + (lambda () (send-txt-to-ghci (org-trim full-body)) (send-eoe)))) + (`value + ;; We first compute the value and store it, + ;; ignoring any output. + (comint-with-output + (lambda () + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (send-txt-to-ghci (org-trim full-body)) + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (send-eoe))) + ;; We now display and capture the value. + (comint-with-output + (lambda() + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__\n") + (send-eoe)))))) + (results (mapcar #'org-strip-quotes + (cdr (member org-babel-haskell-eoe + (reverse (mapcar #'org-trim raw))))))) + (org-babel-reassemble-table + (let ((result + (pcase result-type + (`output (mapconcat #'identity (reverse results) "\n")) + (`value (car results))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (when result (org-babel-script-escape result)))) + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colname-names params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rowname-names params)))))))) + (defun org-babel-execute:haskell (body params) "Execute a block of Haskell code." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 868a56e63..5049bc01a 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -86,8 +86,8 @@ (ert-deftest ob-haskell/hello-world-output () (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-nothing () - :expected-result :failed - (should (equal "" + ;; GHCi prints the value on standard output. So, the last value is part of the output. + (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () @@ -391,12 +391,10 @@ (ert-deftest ob-haskell/results-value-2 () (ert-deftest ob-haskell/results-value-3 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) (ert-deftest ob-haskell/results-value-4 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" " putStrLn \"3\" return () -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #7: 0006-ob-haskell-Implement-sessions.patch --] [-- Type: text/x-patch, Size: 8897 bytes --] From b9fb970c925235be16cd04496235ed13eb31fb63 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:06:44 +0100 Subject: [PATCH 6/7] ob-haskell: Implement sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Implement sessions. (org-babel-haskell-with-session): New macro to manage sessions. (org-babel-interpret-haskell): Refactor code. Use `org-babel-haskell-with-session` to manage sessions. (org-babel-prep-session:haskell): Don't ignore the PARAMS argument. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to sessions. --- lisp/ob-haskell.el | 98 +++++++++++++++++++++++----- testing/lisp/test-ob-haskell-ghci.el | 47 +++++++++---- 2 files changed, 116 insertions(+), 29 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 6cec21217..cd930266c 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -77,6 +77,32 @@ (defcustom org-babel-haskell-compiler "ghc" (defconst org-babel-header-args:haskell '((compile . :any)) "Haskell-specific header arguments.") + +(defun org-babel-haskell-with-session--worker (params todo) + "See `org-babel-haskell-with-session'." + (let* ((sn (cdr (assq :session params))) + (session (org-babel-haskell-initiate-session sn params)) + (one-shot (equal sn "none"))) + (unwind-protect + (funcall todo session) + (when (and one-shot (buffer-live-p session)) + ;; As we don't control how the session temporary buffer is + ;; created, we need to explicitly work around the hooks and + ;; query functions. + (with-current-buffer session + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer session))))))) + +(defmacro org-babel-haskell-with-session (session-symbol params &rest body) + "Get the session identified by PARAMS and run BODY with it. + +Get or create a session, as needed to match PARAMS. Assign the session to +SESSION-SYMBOL. Execute BODY. Destroy the session if needed. +Return the value of the last form of BODY." + (declare (indent 2) (debug (symbolp form body))) + `(org-babel-haskell-with-session--worker ,params (lambda (,session-symbol) ,@body))) + (defun org-babel-haskell-execute (body params) "This function should only be called by `org-babel-execute:haskell'." (let* ((tmp-src-file (org-babel-temp-file "Haskell-src-" ".hs")) @@ -185,22 +211,64 @@ (defun org-babel-execute:haskell (body params) (org-babel-interpret-haskell body params) (org-babel-haskell-execute body params)))) -(defun org-babel-haskell-initiate-session (&optional _session _params) + + + +;; Variable defined in inf-haskell (haskell-mode package). +(defvar inferior-haskell-buffer) + +(defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -If there is not a current inferior-process-buffer in SESSION -then create one. Return the initialized session." +Return the initialized session, i.e. the buffer for this session. +When SESSION-NAME is nil, use a global session named +\"*ob-haskell*\". When SESSION-NAME is the string \"none\", use +a temporary buffer. Else, (re)use the session named +SESSION-NAME. The buffer name is the session name. See also +`org-babel-haskell-with-session'." (org-require-package 'inf-haskell "haskell-mode") - (or (get-buffer "*haskell*") - (save-window-excursion - (run-haskell) - (sleep-for 0.25) - ;; Disable secondary prompt: If we do not do this, - ;; org-comint may treat secondary prompts as a part of - ;; output. - (org-babel-comint-input-command - (current-buffer) - ":set prompt-cont \"\"") - (current-buffer)))) + (cond + ((equal "none" session-name) + ;; Temporary buffer name. + (setq session-name (generate-new-buffer-name " *ob-haskell-tmp*"))) + ((eq nil session-name) + ;; The global default session. As haskell-mode is using the buffer + ;; named "*haskell*", we stay away from it. + (setq session-name "*ob-haskell*")) + ((not (stringp session-name)) + (error "session-name must be a string"))) + (let ((session (get-buffer session-name))) + ;; NOTE: By construction, as SESSION-NAME is a string, session is + ;; either nil or a live buffer. + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (let ((inferior-haskell-buffer session)) + ;; As inferior-haskell expects the buffer to be named + ;; "*haskell*", we rename it, unless the user explicitly + ;; requested to use the name "*haskell*". + (when (not (equal "*haskell*" session-name)) + (when (bufferp session) + (when (bufferp "*haskell*") + (user-error "Conflicting buffer '*haskell*', rename it or kill it")) + (with-current-buffer session (rename-buffer "*haskell*")))) + (unwind-protect + ;; We protect default-directory. + (let ((default-directory default-directory)) + (run-haskell) + (sleep-for 0.25) + (setq session inferior-haskell-buffer)) + (when (and (not (equal "*haskell*" session-name)) + (bufferp session)) + (with-current-buffer session (rename-buffer session-name)))) + ;; Disable secondary prompt: If we do not do this, + ;; org-comint may treat secondary prompts as a part of + ;; output. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session) + )) + session)) + (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." @@ -215,7 +283,7 @@ (defun org-babel-load-session:haskell (session body params) (defun org-babel-prep-session:haskell (session params) "Prepare SESSION according to the header arguments in PARAMS." (save-window-excursion - (let ((buffer (org-babel-haskell-initiate-session session))) + (let ((buffer (org-babel-haskell-initiate-session session params))) (org-babel-comint-in-buffer buffer (mapc (lambda (line) (insert line) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 5049bc01a..adc9f939c 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -106,20 +106,39 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - :expected-result :failed - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) - (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) - (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - )) - -(ert-deftest ob-haskell/no-session-means-one-shot-sessions () - "When no session, use a new session." - :expected-result :failed - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci "" "x=2" nil :unprotected) - (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil))) + (test-ob-haskell-ghci ":session s2" "x=3" nil) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil))) + ) + +(ert-deftest ob-haskell/session-named-none-means-one-shot-sessions () + "When no session, use a new session. +\"none\" is a special name that means `no session'." + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil)))) + +(ert-deftest ob-haskell/reuse-variables-in-same-session () + "Reuse variables between blocks using the same session." + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) + +(ert-deftest ob-haskell/may-use-the-*haskell*-session () + "The user may use the special *haskell* buffer." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't run this test")) + (unwind-protect + (progn + (test-ob-haskell-ghci ":session *haskell*" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session *haskell*" "x" nil :unprotected)))) + (with-current-buffer "*haskell*" + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer "*haskell*"))))) + + ;;;; Values -- 2.39.3 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #8: 0007-testing-lisp-test-ob-haskell-ghci.el-Test-output-wit.patch --] [-- Type: text/x-patch, Size: 1815 bytes --] From 3295295f59e46e97205a36c6541182120d750a98 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:27:57 +0200 Subject: [PATCH 7/7] * testing/lisp/test-ob-haskell-ghci.el: Test output without EOL (ob-haskell/output-without-eol-1): (ob-haskell/output-without-eol-2): (ob-haskell/output-without-eol-3): New tests. --- testing/lisp/test-ob-haskell-ghci.el | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index adc9f939c..212e0cb6f 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -171,6 +171,38 @@ (ert-deftest ob-haskell/eval-strings () "Evaluation of strings." (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) +;;;; Output without EOL +;; + +(ert-deftest ob-haskell/output-without-eol-1 () + "Cannot get output from incomplete lines, when entered line by line." + :expected-result :failed + (should (equal "123" + (test-ob-haskell-ghci ":results output" " + putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +")))) + +(ert-deftest ob-haskell/output-without-eol-2 () + "Incomplete output lines are OK when using a multiline block." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +:{ + do putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +:} +")))) + +(ert-deftest ob-haskell/output-without-eol-3 () + "Incomplete output lines are OK on one line." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +do { putStr(\"1\"); putStr(\"2\"); putStr(\"3\"); putStr(\"\\n\") } +")))) ;;;; Local variables (ert-deftest ob-haskell/let-one-line () -- 2.39.3 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-05-21 7:40 ` Bruno Barbier @ 2023-06-02 8:44 ` Ihor Radchenko 2023-08-10 12:51 ` Ihor Radchenko 2023-09-07 14:21 ` Bruno Barbier 0 siblings, 2 replies; 26+ messages in thread From: Ihor Radchenko @ 2023-06-02 8:44 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 4356 bytes --] Bruno Barbier <brubar.cs@gmail.com> writes: >> I can see that you limited the tests scope to :session blocks. >> Would it be possible to extend the existing tests to :compile yes case? >> From a glance, it does not look like you need to change much - Haskell >> behaviour should be similar with or without ghci. > > Except for one line expressions, GHCi inputs and haskell modules are > two different things. I doubt there will be much to share; that's why > I've focused from the start on GHCi only. Fair point. I thought that the differences are less significant. >>> + (sleep-for 0.25) >>> + ;; Disable secondary prompt. >> >> It would be useful to explain the purpose of disabling the secondary >> prompt in the source code comment itself, not just in the commit >> message. It will improve readability. > > Are you reviewing your own improvements ? :-) Sure. Why not :3 Nobody guaranteed that my code is free of errors. > Fixed. I've copied/pasted your explanation in the code. Thanks! >> Also, session may be a killed buffer object. It is still a buffer, but >> not usable. See `buffer-live-p'. > > By construction, if 'session' is a buffer, then, it is a live buffer. You are probably right. Even though killed buffer objects may appear in Elisp (28.10 Killing Buffers), their buffer names should be nil, and thus they cannot be returned by `get-buffer'. >>> + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) >>> + (with-current-buffer session (rename-buffer "*haskell*"))) >> >> So, you are now renaming the unique session buffer back to "*haskell*". >> And never rename it back to expected :session <value>. Users might be confused. > > I do rename it back once inf-haskell has initialized the buffer (after > run-haskell in the last version). A comment would help to clarify things for the readers. >>> + (save-window-excursion >>> + ;; We don't use `run-haskell' to not popup the buffer. >>> + ;; And we protect default-directory. >>> + (let ((default-directory default-directory)) >>> + (inferior-haskell-start-process)) >> >> This is a workaround for a nasty side effect of running >> `inferior-haskell-start-process'. We should report this to haskell-mode >> developers, leaving appropriate comment in the code. > > About 'run-haskell', I reverted my change: we're inside a > 'save-window-excursion', which looks like the standard way to get rid > of unwanted popups (like ob-shell does). > > About 'default-directory', I'm not sure. Maybe the side effect is done > on purpose in inf-haskell. I can see the haskell-mode overrides default-directory with `inferior-haskell-root-dir', running ghci in that directory, if it is non-nil. Even with your let binding, it is calling for trouble when source block uses :dir header argument. Maybe we can bind `inferior-haskell-root-dir' to `default-directory' instead? `default-directory' is modified according to :dir by ob-core.el when necessary. > The function 'putStr' output the string without a newline on stdout > (as opposed to the function putStrLn that does add a newline). > > So, in GHCi, entering: > > putStr("4") > > outputs "4" on stdout, then GHCi outputs the prompt, so we get: > > 4ghci> > > In the end, 'org-babel-comint-with-output' gets this > 1ghci> 2ghci> 3ghci> > ghci> org-babel-haskell-eoe > ghci> ghci> > > and filters out everything as being GHCi prompts and the EOE. > > I'm not really expecting this to be fixed; I just wanted to record the > fact. We actually might be able to deal with this if we change the prompt and update comint-prompt-regexp to something more accurate. >>> Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions >>> >>> + (org-babel-haskell-with-session >> >> This kind of names are usually dedicated to macro calls. But >> `org-babel-haskell-with-session' is instead a function. I think a macro >> will be better. And you will be able to get rid of unnecessary lambda. > > That looks kind of complicated just to avoid one lambda in one call. > But, as I couldn't find a better name, I've translated it into a > macro. I think you misunderstood what I meant. See the attached diff on top of your patches that simplifies things a bit. [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #2: example.diff --] [-- Type: text/x-patch, Size: 2444 bytes --] diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index cd930266c..a8240d02b 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -77,31 +77,28 @@ (defcustom org-babel-haskell-compiler "ghc" (defconst org-babel-header-args:haskell '((compile . :any)) "Haskell-specific header arguments.") - -(defun org-babel-haskell-with-session--worker (params todo) - "See `org-babel-haskell-with-session'." - (let* ((sn (cdr (assq :session params))) - (session (org-babel-haskell-initiate-session sn params)) - (one-shot (equal sn "none"))) - (unwind-protect - (funcall todo session) - (when (and one-shot (buffer-live-p session)) - ;; As we don't control how the session temporary buffer is - ;; created, we need to explicitly work around the hooks and - ;; query functions. - (with-current-buffer session - (let ((kill-buffer-query-functions nil) - (kill-buffer-hook nil)) - (kill-buffer session))))))) - (defmacro org-babel-haskell-with-session (session-symbol params &rest body) "Get the session identified by PARAMS and run BODY with it. -Get or create a session, as needed to match PARAMS. Assign the session to -SESSION-SYMBOL. Execute BODY. Destroy the session if needed. -Return the value of the last form of BODY." +Get or create a session, as needed to match PARAMS. Assign the +session to SESSION-SYMBOL. Execute BODY. Destroy the session if +needed. Return the value of the last form of BODY." (declare (indent 2) (debug (symbolp form body))) - `(org-babel-haskell-with-session--worker ,params (lambda (,session-symbol) ,@body))) + `(let* ((params ,params) + (sn (cdr (assq :session params))) + (session (org-babel-haskell-initiate-session sn params)) + (,session-symbol session) + (one-shot (equal sn "none"))) + (unwind-protect + (progn ,@body) + (when (and one-shot (buffer-live-p session)) + ;; As we don't control how the session temporary buffer is + ;; created, we need to explicitly work around the hooks and + ;; query functions. + (with-current-buffer session + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer session))))))) (defun org-babel-haskell-execute (body params) "This function should only be called by `org-babel-execute:haskell'." [-- Attachment #3: Type: text/plain, Size: 1782 bytes --] >> When using `cl-labels', please prefer longer, more descriptive function >> names. These functions do not have a docstring and I now am left >> guessing and reading the function code repeatedly to understand the >> usage. > > I tried to use more descriptive names. I hope it's easier to read now. Yes. Thanks! >>> + (full-body (org-babel-expand-body:generic >>> + body params >>> + (org-babel-variable-assignments:haskell params))) >> >> I think we want `org-babel-expand-src-block' here instead of using >> semi-internal ob-core.el parts. > > Are you sure about this ? I didn't modify this part and I didn't see > this function used in other backends. I've also checked ob-python and > ob-shell: they both use the same code as above. Well. You are right. Not that I like it. Let's leave this be until I find time to refactor ob-core API. >>> - (let ((buffer (org-babel-haskell-initiate-session session))) >>> + (let ((buffer (org-babel-haskell-initiate-session session params))) >> >> PARAMS argument is ignored by `org-babel-haskell-initiate-session'. I am >> not sure why you are trying to pass it here. > > We have the PARAMS, and, org-babel-haskell-initiate-session has a > PARAMS arguments. So, at the API level, I think it's better to > propagate it than to ignore it. But you're right that, today, the > current implementation ignores it. > > I'm fine with dropping that change if you so prefer. I am mostly neutral here. Slightly in favour of keeping things unchanged. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-06-02 8:44 ` Ihor Radchenko @ 2023-08-10 12:51 ` Ihor Radchenko 2023-08-25 19:10 ` Bruno Barbier 2023-09-07 14:21 ` Bruno Barbier 1 sibling, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-08-10 12:51 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode A few months have passed since the last activity in this thread. Bruno, may I know if you are still interested to work on the patch? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-08-10 12:51 ` Ihor Radchenko @ 2023-08-25 19:10 ` Bruno Barbier 0 siblings, 0 replies; 26+ messages in thread From: Bruno Barbier @ 2023-08-25 19:10 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode Hi Ihor, Ihor Radchenko <yantar92@posteo.net> writes: > A few months have passed since the last activity in this thread. > Bruno, may I know if you are still interested to work on the patch? Thanks for the reminder. I'm definitely interested to closing that thread. I'll review my last changes and send my updated patch. Thank you for your patience, Bruno. > -- > Ihor Radchenko // yantar92, > Org mode contributor, > Learn more about Org mode at <https://orgmode.org/>. > Support Org development at <https://liberapay.com/org-mode>, > or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-06-02 8:44 ` Ihor Radchenko 2023-08-10 12:51 ` Ihor Radchenko @ 2023-09-07 14:21 ` Bruno Barbier 2023-09-08 8:23 ` Ihor Radchenko 1 sibling, 1 reply; 26+ messages in thread From: Bruno Barbier @ 2023-09-07 14:21 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 4826 bytes --] Hi Ihor, Sorry for the delay, thanks again for the ping. Ihor Radchenko <yantar92@posteo.net> writes: > Bruno Barbier <brubar.cs@gmail.com> writes: > >>>> + (when (bufferp "*haskell*") (error "Conflicting buffer '*haskell*', rename it or kill it.")) >>>> + (with-current-buffer session (rename-buffer "*haskell*"))) >>> >>> So, you are now renaming the unique session buffer back to "*haskell*". >>> And never rename it back to expected :session <value>. Users might be confused. >> >> I do rename it back once inf-haskell has initialized the buffer (after >> run-haskell in the last version). > > A comment would help to clarify things for the readers. Right. I've improved the comment. Thanks. >>>> + (save-window-excursion >>>> + ;; We don't use `run-haskell' to not popup the buffer. >>>> + ;; And we protect default-directory. >>>> + (let ((default-directory default-directory)) >>>> + (inferior-haskell-start-process)) >>> >> About 'default-directory', I'm not sure. Maybe the side effect is done >> on purpose in inf-haskell. > > I can see the haskell-mode overrides default-directory with > `inferior-haskell-root-dir', running ghci in that directory, if it is > non-nil. Even with your let binding, it is calling for trouble when > source block uses :dir header argument. > > Maybe we can bind `inferior-haskell-root-dir' to `default-directory' > instead? `default-directory' is modified according to :dir by ob-core.el > when necessary. You may be right that we should use `inferior-haskell-root-dir' to tell haskell-mode where to run the interpreter. Done. >> The function 'putStr' output the string without a newline on stdout >> (as opposed to the function putStrLn that does add a newline). >> >> So, in GHCi, entering: >> >> putStr("4") >> >> outputs "4" on stdout, then GHCi outputs the prompt, so we get: >> >> 4ghci> >> >> In the end, 'org-babel-comint-with-output' gets this >> 1ghci> 2ghci> 3ghci> >> ghci> org-babel-haskell-eoe >> ghci> ghci> >> >> and filters out everything as being GHCi prompts and the EOE. >> >> I'm not really expecting this to be fixed; I just wanted to record the >> fact. > > We actually might be able to deal with this if we change the prompt and > update comint-prompt-regexp to something more accurate. I couldn't make it work by simply restricting the prompt. I think it's OK if character by character output doesn't work; ghci is about line based interaction afterall. >>>> Subject: [PATCH 11/13] lisp/ob-haskell.el: Fix how to use sessions >>>> >>>> + (org-babel-haskell-with-session >>> >>> This kind of names are usually dedicated to macro calls. But >>> `org-babel-haskell-with-session' is instead a function. I think a macro >>> will be better. And you will be able to get rid of unnecessary lambda. >> >> That looks kind of complicated just to avoid one lambda in one call. >> But, as I couldn't find a better name, I've translated it into a >> macro. > > I think you misunderstood what I meant. > See the attached diff on top of your patches that simplifies things a > bit. > diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el ... > (defmacro org-babel-haskell-with-session (session-symbol params &rest body) > "Get the session identified by PARAMS and run BODY with it. .. > + `(let* ((params ,params) > + (sn (cdr (assq :session params))) > + (session (org-babel-haskell-initiate-session sn params)) > + (,session-symbol session) > + (one-shot (equal sn "none"))) > + (unwind-protect > + (progn ,@body) I don't think it's correct to create local variables like this in a macro: we need to use uninterned symbols, else we may capture caller variables (params, sn, session and one-shot). I personnaly find it easier when I keep my macros as short as possible, and, to do any non-trivial work in a function: easier to read, to modify and to debug. >>>> - (let ((buffer (org-babel-haskell-initiate-session session))) >>>> + (let ((buffer (org-babel-haskell-initiate-session session params))) >>> >>> PARAMS argument is ignored by `org-babel-haskell-initiate-session'. I am >>> not sure why you are trying to pass it here. >> >> We have the PARAMS, and, org-babel-haskell-initiate-session has a >> PARAMS arguments. So, at the API level, I think it's better to >> propagate it than to ignore it. But you're right that, today, the >> current implementation ignores it. >> >> I'm fine with dropping that change if you so prefer. > > I am mostly neutral here. Slightly in favour of keeping things unchanged. I've removed my change. The function `org-babel-load-session:haskell` now ignores the parameter PARAMS, as before. Thanks for the review. Bruno [-- Attachment #2: 0001-ob-haskell-Add-tests-for-GHCi.patch --] [-- Type: text/x-patch, Size: 13747 bytes --] From 86a5443948fc84a6a412ccf49d0c537608f465a7 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Fri, 18 Nov 2022 20:14:20 +0100 Subject: [PATCH 1/7] ob-haskell: Add tests for GHCi testing/lisp/test-ob-haskell-ghci.el: New file. --- testing/lisp/test-ob-haskell-ghci.el | 430 +++++++++++++++++++++++++++ 1 file changed, 430 insertions(+) create mode 100644 testing/lisp/test-ob-haskell-ghci.el diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el new file mode 100644 index 000000000..920103858 --- /dev/null +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -0,0 +1,430 @@ +;;; test-ob-haskell-ghci.el --- tests for ob-haskell.el GHCi -*- lexical-binding: t; -*- + +;; Copyright (c) 2023 Free Software Foundation, Inc. +;; Authors: Bruno BARBIER <brubar.cs@gmail.com> + +;; This file is part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; + +;;;; Useful references +;; +;; - https://orgmode.org/worg/org-contrib/babel/languages/lang-compat.html +;; - GHCi manual: https://downloads.haskell.org/ghc/latest/docs/users_guide/ghci.html + +;;; Code: +;; + +(require 'org-test "../testing/org-test") +(org-test-for-executable "ghci") +(unless (featurep 'haskell-mode) + (signal 'missing-test-dependency "haskell-mode")) +(unless (featurep 'haskell) + (signal 'missing-test-dependency "haskell")) + + +;;; Helpers +;; + +(defun test-ob-haskell-ghci-checking-buffers (todo) + "Check some buffer related invariants.." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't safely test haskell blocks")) + (prog1 (funcall todo) + (when-let ((hb (get-buffer "*haskell*"))) + ;; We created a "*haskell*" buffer. That shouldn't happen. + (error "'ob-haskell' created a buffer named '*haskell*'")))) + + + +(defun test-ob-haskell-ghci (args content &optional preamble unprotected) + "Execute the code block CONTENT in a new GHCi session; return the result. +Add ARGS to the code block argument line. Insert PREAMBLE +before the code block. When UNPROTECTED is non-nil, check pre/post conditions." + (when (listp content) + (setq content (string-join content "\n"))) + (unless preamble + (setq preamble "")) + (let ((todo (lambda () + (prog1 (org-test-with-temp-text + (concat preamble "\n" "#+begin_src haskell :compile no " + args "\n" "<point>" content "\n#+end_src") + (org-babel-execute-src-block)))))) + (if unprotected (funcall todo) + (test-ob-haskell-ghci-checking-buffers todo)))) + + + +;;; Tests + + +;;;; Hello Worlds. +;; + +(ert-deftest ob-haskell/hello-world-value-pure () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "\"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-value-IO () + (should (equal "Hello World!" + (test-ob-haskell-ghci "" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output () + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-nothing () + :expected-result :failed + (should (equal "" + (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) + +(ert-deftest ob-haskell/hello-world-output-multilines () + :expected-result :failed + (should (equal "Hello World!" + (test-ob-haskell-ghci ":results output" " +:{ +main :: IO () +main = putStrLn \"Hello World!\" +:} + +main +")))) + +;;;; Sessions +;; + +(ert-deftest ob-haskell/sessions-must-not-share-variables () + "Sessions must not share variables." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) + )) + +(ert-deftest ob-haskell/no-session-means-one-shot-sessions () + "When no session, use a new session." + :expected-result :failed + (test-ob-haskell-ghci-with-global-session + (test-ob-haskell-ghci "" "x=2" nil :unprotected) + (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + + +;;;; Values +;; + +(ert-deftest ob-haskell/value-is-the-last-expression () + "Return the value of the last expression." + (should (equal 3 (test-ob-haskell-ghci "" '("1" "1+1" "1+1+1")))) + (should (equal 3 (test-ob-haskell-ghci "" '("x=1" "y=1+1" "x+y"))))) + +(ert-deftest ob-haskell/value-is-the-last-expression-2 () + "Return the value of the last expression." + (should (equal 7 (test-ob-haskell-ghci "" " +putStrLn \"a string\" +return \"useless\" +3+4 +")))) + + + +(ert-deftest ob-haskell/eval-numbers () + "Evaluation of numbers." + (should (equal 7 (test-ob-haskell-ghci "" "7"))) + (should (equal 7.5 (test-ob-haskell-ghci "" "7.5"))) + (should (equal 10.0 (test-ob-haskell-ghci "" "10::Double"))) + (should (equal 10 (test-ob-haskell-ghci "" "10::Int")))) + + +(ert-deftest ob-haskell/eval-strings () + "Evaluation of strings." + (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) + + +;;;; Local variables +(ert-deftest ob-haskell/let-one-line () + "Local definitions on one line." + (should (equal 6 (test-ob-haskell-ghci "" "let { x=2; y=3 } in x*y")))) + +(ert-deftest ob-haskell/let-multilines-1 () + "Local definitions on multiple lines." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let { x=2 + ; y=3 + } + in x*y +:} +")))) + +(ert-deftest ob-haskell/let-multilines-2 () + "Local definitions on multiple lines, relying on indentation." + :expected-result :failed + (should (equal 6 (test-ob-haskell-ghci "" " +:{ + let x=2 + y=3 + in x*y +:} +")))) + +;;;; Declarations with multiple lines. +(ert-deftest ob-haskell/decl-multilines-1 () + "A multiline declaration, then use it." + (should (equal 3 (test-ob-haskell-ghci "" " +:{ +let length' [] = 0 + length' (_:l) = 1 + length' l +:} +length' [1,2,3] +")))) + +(ert-deftest ob-haskell/decl-multilines-2 () + "A multiline declaration, then use it." + (should (equal 5 (test-ob-haskell-ghci "" " +:{ +length' :: [a] -> Int +length' [] = 0 +length' (_:l) = 1 + length' l +:} + +length' [1..5] +")))) + + +(ert-deftest ob-haskell/primes () + "From haskell.org.""" + :expected-result :failed + (should (equal '(2 3 5 7 11 13 17 19 23 29) + (test-ob-haskell-ghci "" " +:{ +primes = filterPrime [2..] where + filterPrime (p:xs) = + p : filterPrime [x | x <- xs, x `mod` p /= 0] +:} + +take 10 primes +")))) + +;;;; Lists +;; + +(ert-deftest ob-haskell/a-simple-list () + "Evaluation of list of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "[1,2,3]")))) + + +(ert-deftest ob-haskell/2D-lists () + "Evaluation of nested lists into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "[[1..3], [4..6]]")))) + +(ert-deftest ob-haskell/2D-lists-multilines () + "Evaluation of nested lists into a table, as multilines." + :expected-result :failed + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" " +:{ +[ [1..3] +, [4..6] +, [7..9] +] +:} +")))) + + +;;;; Tuples +;; + +(ert-deftest ob-haskell/a-simple-tuple () + "Evaluation of tuple of values." + (should (equal '(1 2 3) (test-ob-haskell-ghci "" "(1,2,3)")))) + + +(ert-deftest ob-haskell/2D-tuples () + "Evaluation of nested tuples into a table." + (should (equal '((1 2 3) (4 5 6)) + (test-ob-haskell-ghci "" "((1,2,3), (4,5,6))")))) + +(ert-deftest ob-haskell/2D-tuples-multilines () + "Evaluation of nested tuples into a table, as multilines." + (should (equal '((1 2 3) (4 5 6) (7 8 9)) + (test-ob-haskell-ghci "" " +:{ +( (1,2,3) +, (4,5,6) +, (7,8,9) +) +:} +")))) + + +;;;; Data tables +;; + +(ert-deftest ob-haskell/int-table-data () + "From worg: int-table-data." + (should (equal 10 (test-ob-haskell-ghci ":var t=int-table-data" + "sum [sum r | r <- t]" + "#+name: int-table-data + | 1 | 2 | + | 3 | 4 |")))) + +(ert-deftest ob-haskell/float-table-data () + "From worg: float-table-data." + (should (equal 11.0 (test-ob-haskell-ghci ":var t=float-table-data" + "sum [sum r | r <- t]" + "#+name: float-table-data + | 1.1 | 2.2 | + | 3.3 | 4.4 |")))) + +(ert-deftest ob-haskell/string-table-data () + "From worg: string-table-data." + (should (equal "abcd" (test-ob-haskell-ghci ":var t=string-table-data" + "concat [concat r | r <- t]" + "#+name: string-table-data + | a | b | + | c | d |")))) + +;;;; Reuse results +;; +(ert-deftest ob-haskell/reuse-table () + "Reusing a computed tables." + (should (equal 78 (test-ob-haskell-ghci ":var t=a-table" + "sum [sum r | r <- t]" + "#+name: a-table +#+begin_src haskell + [ [x..x+2] | x <- [1,4 .. 12] ] +#+end_src +")))) + + +;;;; Not defined errors +;; + +(ert-deftest ob-haskell/not-defined () + "Evaluation of undefined variables." + :expected-result :failed + (should-error (test-ob-haskell-ghci "" "notDefined :: IO Int"))) + + +(ert-deftest ob-haskell/not-defined-then-defined-1 () + "Evaluation of undefined variables. +This is a valid haskell source, but, invalid when entered one +line at a time in GHCi." + :expected-result :failed + (should-error (test-ob-haskell-ghci "" " +v :: Int +v = 4 +"))) + + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed () + "Like not-defined-then-defined-1, but using the mutiline marks." + :expected-result :failed + (let ((r (test-ob-haskell-ghci "" " +:{ + v :: Int + v = 4 +:} +"))) + (should (eq nil r)))) + +(ert-deftest ob-haskell/not-defined-then-defined-1-fixed-2 () + "Like not-defined-then-defined-1, but using one line." + (should (eq nil (test-ob-haskell-ghci "" "v = 4 :: Int")))) + + + +(ert-deftest ob-haskell/not-defined-then-defined-2 () + "Evaluation of undefined variables, followed by a correct one." + ;; ghci output is: + ;; | <interactive>:2:1-4: error: + ;; | • Variable not in scope: main :: IO () + ;; | • Perhaps you meant ‘min’ (imported from Prelude) + ;; | Hello, World! + ;; and ob-haskell just reports the last line "Hello, World!". + (should (string-match "Variable not in scope" + (test-ob-haskell-ghci ":results output" " +main :: IO () +main = putStrLn \"Hello, World!\" +main +")))) + +;;;; Imports +;; + +(ert-deftest ob-haskell/import () + "Import and use library." + (should (equal 65 (test-ob-haskell-ghci "" " +import Data.IORef +r <- newIORef 65 +readIORef r +")))) + +(ert-deftest ob-haskell/import-with-vars () + "Import and use library with vars." + (should (equal 65 (test-ob-haskell-ghci ":var x=65" " +import Data.IORef +r <- newIORef x +readIORef r +")))) + +;;;; What is the result? +;; + +(ert-deftest ob-haskell/results-value-1 () + "Don't confuse output and values: nothing." + (should (equal nil (test-ob-haskell-ghci ":results value" "return ()")))) + +(ert-deftest ob-haskell/results-value-2 () + "Don't confuse output and values: a list." + (should (equal '(1 2) (test-ob-haskell-ghci ":results value" "return [1,2]")))) + +(ert-deftest ob-haskell/results-value-3 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) + +(ert-deftest ob-haskell/results-value-4 () + "Don't confuse output and values: nothing." + :expected-result :failed + (should (equal nil (test-ob-haskell-ghci ":results value" " +putStrLn \"3\" +return () +")))) + + +;;;; GHCi commands +;; + +(ert-deftest ob-haskell/ghci-type () + "The ghci meta command ':type'." + (should (equal "n :: Int" + (test-ob-haskell-ghci ":results output" "let n=3::Int\n:type n")))) + +(ert-deftest ob-haskell/ghci-info () + "The ghci meta command ':info' ." + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) + + +(provide 'test-ob-haskell-ghci) + +;;; test-ob-haskell-ghci.el ends here -- 2.41.0 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #3: 0002-org-babel-haskell-initiate-session-Remove-secondary-.patch --] [-- Type: text/x-patch, Size: 1402 bytes --] From b0e162a4238be505844a460bea7e4b42a72402ad Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:20:22 +0100 Subject: [PATCH 2/7] org-babel-haskell-initiate-session: Remove secondary prompt * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Set secondary prompt to "". If we do not do this, org-comint may treat secondary prompts as a part of output. --- lisp/ob-haskell.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 5babb2bb9..7e36c29e6 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -169,7 +169,16 @@ (defun org-babel-haskell-initiate-session (&optional _session _params) then create one. Return the initialized session." (org-require-package 'inf-haskell "haskell-mode") (or (get-buffer "*haskell*") - (save-window-excursion (run-haskell) (sleep-for 0.25) (current-buffer)))) + (save-window-excursion + (run-haskell) + (sleep-for 0.25) + ;; Disable secondary prompt: If we do not do this, + ;; org-comint may treat secondary prompts as a part of + ;; output. + (org-babel-comint-input-command + (current-buffer) + ":set prompt-cont \"\"") + (current-buffer)))) (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." -- 2.41.0 [-- Attachment #4: 0003-testing-lisp-test-ob-haskell-ghci.el-Fix-some-tests.patch --] [-- Type: text/x-patch, Size: 1508 bytes --] From 5164ef6db8e9d722536b0dbb00523202f1ae917c Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:25:19 +0100 Subject: [PATCH 3/7] * testing/lisp/test-ob-haskell-ghci.el: Fix some tests (ob-haskell/2D-lists-multilines): (ob-haskell/ghci-info): Fix incorrect test assertions. --- testing/lisp/test-ob-haskell-ghci.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 920103858..e34dfacd9 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -237,8 +237,7 @@ (ert-deftest ob-haskell/2D-lists () (ert-deftest ob-haskell/2D-lists-multilines () "Evaluation of nested lists into a table, as multilines." - :expected-result :failed - (should (equal '((1 2 3) (4 5 6)) + (should (equal '((1 2 3) (4 5 6) (7 8 9)) (test-ob-haskell-ghci "" " :{ [ [1..3] @@ -421,8 +420,9 @@ (ert-deftest ob-haskell/ghci-type () (ert-deftest ob-haskell/ghci-info () "The ghci meta command ':info' ." - (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" - (test-ob-haskell-ghci ":results output" ":info repeat")))) + (should (string-match-p + "repeat :: a -> \\[a\\][ \t]+-- Defined in ‘GHC.List’" + (test-ob-haskell-ghci ":results output" ":info repeat")))) (provide 'test-ob-haskell-ghci) -- 2.41.0 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #5: 0004-testing-lisp-test-ob-haskell-ghci.el-Enable-fixed-te.patch --] [-- Type: text/x-patch, Size: 2122 bytes --] From 5bf5f05f1d76d6128d56d48b9b23a767ba458a2d Mon Sep 17 00:00:00 2001 From: Ihor Radchenko <yantar92@posteo.net> Date: Fri, 24 Mar 2023 11:26:00 +0100 Subject: [PATCH 4/7] * testing/lisp/test-ob-haskell-ghci.el: Enable fixed tests (ob-haskell/hello-world-output-multilines): (ob-haskell/let-multilines-1): (ob-haskell/let-multilines-2): (ob-haskell/primes): (ob-haskell/not-defined-then-defined-1-fixed): Re-enable tests. --- testing/lisp/test-ob-haskell-ghci.el | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index e34dfacd9..c56ad8f51 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -93,7 +93,6 @@ (ert-deftest ob-haskell/hello-world-output-nothing () (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () - :expected-result :failed (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" " :{ @@ -163,7 +162,6 @@ (ert-deftest ob-haskell/let-one-line () (ert-deftest ob-haskell/let-multilines-1 () "Local definitions on multiple lines." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let { x=2 @@ -175,7 +173,6 @@ (ert-deftest ob-haskell/let-multilines-1 () (ert-deftest ob-haskell/let-multilines-2 () "Local definitions on multiple lines, relying on indentation." - :expected-result :failed (should (equal 6 (test-ob-haskell-ghci "" " :{ let x=2 @@ -210,7 +207,6 @@ (ert-deftest ob-haskell/decl-multilines-2 () (ert-deftest ob-haskell/primes () "From haskell.org.""" - :expected-result :failed (should (equal '(2 3 5 7 11 13 17 19 23 29) (test-ob-haskell-ghci "" " :{ @@ -336,7 +332,6 @@ (ert-deftest ob-haskell/not-defined-then-defined-1 () (ert-deftest ob-haskell/not-defined-then-defined-1-fixed () "Like not-defined-then-defined-1, but using the mutiline marks." - :expected-result :failed (let ((r (test-ob-haskell-ghci "" " :{ v :: Int -- 2.41.0 [-- Attachment #6: 0005-lisp-ob-haskell-Request-the-last-value-from-GHCi.patch --] [-- Type: text/x-patch, Size: 6772 bytes --] From cb35845d9b85ad37a90e6858651ac826679e41ae Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 09:59:31 +0100 Subject: [PATCH 5/7] lisp/ob-haskell: Request the last value from GHCi * lisp/ob-haskell.el (org-babel-interpret-haskell): When the result type is 'value, use the last value as defined by GHCi. (org-babel-haskell-eoe): New default value. (org-babel-interpret-haskell): Update for the new value of `org-babel-haskell-eoe'. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to output/value. --- lisp/ob-haskell.el | 80 ++++++++++++++++++---------- testing/lisp/test-ob-haskell-ghci.el | 6 +-- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 7e36c29e6..55687769f 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -61,7 +61,7 @@ (defvar org-babel-default-header-args:haskell (defvar org-babel-haskell-lhs2tex-command "lhs2tex") -(defvar org-babel-haskell-eoe "\"org-babel-haskell-eoe\"") +(defvar org-babel-haskell-eoe "org-babel-haskell-eoe") (defvar haskell-prompt-regexp) @@ -127,34 +127,56 @@ (defun org-babel-interpret-haskell (body params) (lambda () (setq-local comint-prompt-regexp (concat haskell-prompt-regexp "\\|^λ?> ")))) - (let* ((session (cdr (assq :session params))) - (result-type (cdr (assq :result-type params))) - (full-body (org-babel-expand-body:generic - body params - (org-babel-variable-assignments:haskell params))) - (session (org-babel-haskell-initiate-session session params)) - (comint-preoutput-filter-functions - (cons 'ansi-color-filter-apply comint-preoutput-filter-functions)) - (raw (org-babel-comint-with-output - (session org-babel-haskell-eoe nil full-body) - (insert (org-trim full-body)) - (comint-send-input nil t) - (insert org-babel-haskell-eoe) - (comint-send-input nil t))) - (results (mapcar #'org-strip-quotes - (cdr (member org-babel-haskell-eoe - (reverse (mapcar #'org-trim raw))))))) - (org-babel-reassemble-table - (let ((result - (pcase result-type - (`output (mapconcat #'identity (reverse results) "\n")) - (`value (car results))))) - (org-babel-result-cond (cdr (assq :result-params params)) - result (when result (org-babel-script-escape result)))) - (org-babel-pick-name (cdr (assq :colname-names params)) - (cdr (assq :colname-names params))) - (org-babel-pick-name (cdr (assq :rowname-names params)) - (cdr (assq :rowname-names params)))))) + (org-babel-haskell-with-session session params + (cl-labels + ((send-txt-to-ghci (txt) + (insert txt) (comint-send-input nil t)) + (send-eoe () + (send-txt-to-ghci (concat "putStrLn \"" org-babel-haskell-eoe "\"\n"))) + (comint-with-output (todo) + (let ((comint-preoutput-filter-functions + (cons 'ansi-color-filter-apply + comint-preoutput-filter-functions))) + (org-babel-comint-with-output + (session org-babel-haskell-eoe nil nil) + (funcall todo))))) + (let* ((result-type (cdr (assq :result-type params))) + (full-body (org-babel-expand-body:generic + body params + (org-babel-variable-assignments:haskell params))) + (raw (pcase result-type + (`output + (comint-with-output + (lambda () (send-txt-to-ghci (org-trim full-body)) (send-eoe)))) + (`value + ;; We first compute the value and store it, + ;; ignoring any output. + (comint-with-output + (lambda () + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__=()::()\n") + (send-txt-to-ghci (org-trim full-body)) + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__=it\n") + (send-eoe))) + ;; We now display and capture the value. + (comint-with-output + (lambda() + (send-txt-to-ghci "__LAST_VALUE_IMPROBABLE_NAME__\n") + (send-eoe)))))) + (results (mapcar #'org-strip-quotes + (cdr (member org-babel-haskell-eoe + (reverse (mapcar #'org-trim raw))))))) + (org-babel-reassemble-table + (let ((result + (pcase result-type + (`output (mapconcat #'identity (reverse results) "\n")) + (`value (car results))))) + (org-babel-result-cond (cdr (assq :result-params params)) + result (when result (org-babel-script-escape result)))) + (org-babel-pick-name (cdr (assq :colname-names params)) + (cdr (assq :colname-names params))) + (org-babel-pick-name (cdr (assq :rowname-names params)) + (cdr (assq :rowname-names params)))))))) + (defun org-babel-execute:haskell (body params) "Execute a block of Haskell code." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index c56ad8f51..9cdc763d9 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -88,8 +88,8 @@ (ert-deftest ob-haskell/hello-world-output () (test-ob-haskell-ghci ":results output" "putStrLn \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-nothing () - :expected-result :failed - (should (equal "" + ;; GHCi prints the value on standard output. So, the last value is part of the output. + (should (equal "Hello World!" (test-ob-haskell-ghci ":results output" "return \"Hello World!\"")))) (ert-deftest ob-haskell/hello-world-output-multilines () @@ -393,12 +393,10 @@ (ert-deftest ob-haskell/results-value-2 () (ert-deftest ob-haskell/results-value-3 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" "putStrLn \"3\"")))) (ert-deftest ob-haskell/results-value-4 () "Don't confuse output and values: nothing." - :expected-result :failed (should (equal nil (test-ob-haskell-ghci ":results value" " putStrLn \"3\" return () -- 2.41.0 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #7: 0006-ob-haskell-Implement-sessions.patch --] [-- Type: text/x-patch, Size: 8393 bytes --] From 4beb4e6c1abe2c5a560662903f236a6c1ab3d44f Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 25 Mar 2023 10:06:44 +0100 Subject: [PATCH 6/7] ob-haskell: Implement sessions * lisp/ob-haskell.el (org-babel-haskell-initiate-session): Implement sessions. (org-babel-haskell-with-session): New macro to manage sessions. (org-babel-interpret-haskell): Refactor code. Use `org-babel-haskell-with-session` to manage sessions. * testing/lisp/test-ob-haskell-ghci.el: Update tests related to sessions. --- lisp/ob-haskell.el | 96 ++++++++++++++++++++++++---- testing/lisp/test-ob-haskell-ghci.el | 47 ++++++++++---- 2 files changed, 115 insertions(+), 28 deletions(-) diff --git a/lisp/ob-haskell.el b/lisp/ob-haskell.el index 55687769f..8f6d5aebd 100644 --- a/lisp/ob-haskell.el +++ b/lisp/ob-haskell.el @@ -77,6 +77,32 @@ (defcustom org-babel-haskell-compiler "ghc" (defconst org-babel-header-args:haskell '((compile . :any)) "Haskell-specific header arguments.") + +(defun org-babel-haskell-with-session--worker (params todo) + "See `org-babel-haskell-with-session'." + (let* ((sn (cdr (assq :session params))) + (session (org-babel-haskell-initiate-session sn params)) + (one-shot (equal sn "none"))) + (unwind-protect + (funcall todo session) + (when (and one-shot (buffer-live-p session)) + ;; As we don't control how the session temporary buffer is + ;; created, we need to explicitly work around the hooks and + ;; query functions. + (with-current-buffer session + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer session))))))) + +(defmacro org-babel-haskell-with-session (session-symbol params &rest body) + "Get the session identified by PARAMS and run BODY with it. + +Get or create a session, as needed to match PARAMS. Assign the session to +SESSION-SYMBOL. Execute BODY. Destroy the session if needed. +Return the value of the last form of BODY." + (declare (indent 2) (debug (symbolp form body))) + `(org-babel-haskell-with-session--worker ,params (lambda (,session-symbol) ,@body))) + (defun org-babel-haskell-execute (body params) "This function should only be called by `org-babel-execute:haskell'." (let* ((tmp-src-file (org-babel-temp-file "Haskell-src-" ".hs")) @@ -185,22 +211,64 @@ (defun org-babel-execute:haskell (body params) (org-babel-interpret-haskell body params) (org-babel-haskell-execute body params)))) -(defun org-babel-haskell-initiate-session (&optional _session _params) + + + +;; Variable defined in inf-haskell (haskell-mode package). +(defvar inferior-haskell-buffer) + +(defun org-babel-haskell-initiate-session (&optional session-name _params) "Initiate a haskell session. -If there is not a current inferior-process-buffer in SESSION -then create one. Return the initialized session." +Return the initialized session, i.e. the buffer for this session. +When SESSION-NAME is nil, use a global session named +\"*ob-haskell*\". When SESSION-NAME is the string \"none\", use +a temporary buffer. Else, (re)use the session named +SESSION-NAME. The buffer name is the session name. See also +`org-babel-haskell-with-session'." (org-require-package 'inf-haskell "haskell-mode") - (or (get-buffer "*haskell*") - (save-window-excursion - (run-haskell) - (sleep-for 0.25) - ;; Disable secondary prompt: If we do not do this, - ;; org-comint may treat secondary prompts as a part of - ;; output. - (org-babel-comint-input-command - (current-buffer) - ":set prompt-cont \"\"") - (current-buffer)))) + (cond + ((equal "none" session-name) + ;; Temporary buffer name. + (setq session-name (generate-new-buffer-name " *ob-haskell-tmp*"))) + ((eq nil session-name) + ;; The global default session. As haskell-mode is using the buffer + ;; named "*haskell*", we stay away from it. + (setq session-name "*ob-haskell*")) + ((not (stringp session-name)) + (error "session-name must be a string"))) + (let ((session (get-buffer session-name))) + ;; NOTE: By construction, as SESSION-NAME is a string, session is + ;; either nil or a live buffer. + (save-window-excursion + (or (org-babel-comint-buffer-livep session) + (let ((inferior-haskell-buffer session)) + ;; As inferior-haskell expects the buffer to be named + ;; "*haskell*", we temporarily rename it while executing + ;; `run-haskell' (unless the user explicitly requested to + ;; use the name "*haskell*"). + (when (not (equal "*haskell*" session-name)) + (when (bufferp session) + (when (bufferp "*haskell*") + (user-error "Conflicting buffer '*haskell*', rename it or kill it")) + (with-current-buffer session (rename-buffer "*haskell*")))) + (unwind-protect + (let ((inferior-haskell-root-dir default-directory)) + (run-haskell) + (sleep-for 0.25) + (setq session inferior-haskell-buffer)) + (when (and (not (equal "*haskell*" session-name)) + (bufferp session)) + (with-current-buffer session (rename-buffer session-name)))) + ;; Disable secondary prompt: If we do not do this, + ;; org-comint may treat secondary prompts as a part of + ;; output. + (org-babel-comint-input-command + session + ":set prompt-cont \"\"") + session) + )) + session)) + (defun org-babel-load-session:haskell (session body params) "Load BODY into SESSION." diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 9cdc763d9..9ccc60e90 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -108,20 +108,39 @@ (ert-deftest ob-haskell/hello-world-output-multilines () (ert-deftest ob-haskell/sessions-must-not-share-variables () "Sessions must not share variables." - :expected-result :failed - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci ":session s1" "x=2" nil :unprotected) - (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - (test-ob-haskell-ghci ":session s2" "x=3" nil :unprotected) - (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil :unprotected))) - )) - -(ert-deftest ob-haskell/no-session-means-one-shot-sessions () - "When no session, use a new session." - :expected-result :failed - (test-ob-haskell-ghci-with-global-session - (test-ob-haskell-ghci "" "x=2" nil :unprotected) - (should-not (equal 2 (test-ob-haskell-ghci "" "x" nil :unprotected))))) + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x" nil))) + (test-ob-haskell-ghci ":session s2" "x=3" nil) + (should-not (equal 3 (test-ob-haskell-ghci ":session s1" "x" nil))) + ) + +(ert-deftest ob-haskell/session-named-none-means-one-shot-sessions () + "When no session, use a new session. +\"none\" is a special name that means `no session'." + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil))) + (test-ob-haskell-ghci ":session none" "x=2" nil) + (should-not (equal 2 (test-ob-haskell-ghci ":session \"none\"" "x" nil)))) + +(ert-deftest ob-haskell/reuse-variables-in-same-session () + "Reuse variables between blocks using the same session." + (test-ob-haskell-ghci ":session s1" "x=2" nil) + (should (equal 2 (test-ob-haskell-ghci ":session s1" "x")))) + +(ert-deftest ob-haskell/may-use-the-*haskell*-session () + "The user may use the special *haskell* buffer." + (when (get-buffer "*haskell*") + (error "A buffer named '*haskell*' exists. Can't run this test")) + (unwind-protect + (progn + (test-ob-haskell-ghci ":session *haskell*" "x=2" nil :unprotected) + (should (equal 2 (test-ob-haskell-ghci ":session *haskell*" "x" nil :unprotected)))) + (with-current-buffer "*haskell*" + (let ((kill-buffer-query-functions nil) + (kill-buffer-hook nil)) + (kill-buffer "*haskell*"))))) + + ;;;; Values -- 2.41.0 [-- Warning: decoded text below may be mangled, UTF-8 assumed --] [-- Attachment #8: 0007-testing-lisp-test-ob-haskell-ghci.el-Test-output-wit.patch --] [-- Type: text/x-patch, Size: 1815 bytes --] From d0f0d40d570a6b63aa0b4ad3ae6f1b67a32e99a3 Mon Sep 17 00:00:00 2001 From: Bruno BARBIER <brubar.cs@gmail.com> Date: Sat, 29 Apr 2023 10:27:57 +0200 Subject: [PATCH 7/7] * testing/lisp/test-ob-haskell-ghci.el: Test output without EOL (ob-haskell/output-without-eol-1): (ob-haskell/output-without-eol-2): (ob-haskell/output-without-eol-3): New tests. --- testing/lisp/test-ob-haskell-ghci.el | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/testing/lisp/test-ob-haskell-ghci.el b/testing/lisp/test-ob-haskell-ghci.el index 9ccc60e90..e64e87203 100644 --- a/testing/lisp/test-ob-haskell-ghci.el +++ b/testing/lisp/test-ob-haskell-ghci.el @@ -173,6 +173,38 @@ (ert-deftest ob-haskell/eval-strings () "Evaluation of strings." (should (equal "a string" (test-ob-haskell-ghci "" "\"a string\"")))) +;;;; Output without EOL +;; + +(ert-deftest ob-haskell/output-without-eol-1 () + "Cannot get output from incomplete lines, when entered line by line." + :expected-result :failed + (should (equal "123" + (test-ob-haskell-ghci ":results output" " + putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +")))) + +(ert-deftest ob-haskell/output-without-eol-2 () + "Incomplete output lines are OK when using a multiline block." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +:{ + do putStr(\"1\") + putStr(\"2\") + putStr(\"3\") + putStr(\"\\n\") +:} +")))) + +(ert-deftest ob-haskell/output-without-eol-3 () + "Incomplete output lines are OK on one line." + (should (equal "123" + (test-ob-haskell-ghci ":results output" " +do { putStr(\"1\"); putStr(\"2\"); putStr(\"3\"); putStr(\"\\n\") } +")))) ;;;; Local variables (ert-deftest ob-haskell/let-one-line () -- 2.41.0 ^ permalink raw reply related [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-09-07 14:21 ` Bruno Barbier @ 2023-09-08 8:23 ` Ihor Radchenko 2023-09-08 9:49 ` Bruno Barbier 0 siblings, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-09-08 8:23 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: > From 86a5443948fc84a6a412ccf49d0c537608f465a7 Mon Sep 17 00:00:00 2001 > From: Bruno BARBIER <brubar.cs@gmail.com> > Date: Fri, 18 Nov 2022 20:14:20 +0100 > Subject: [PATCH 1/7] ob-haskell: Add tests for GHCi > ... Thanks! Applied, onto main. I also added a missing defvar declaration for `inferior-haskell-root-dir'. https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=91c52e2ab https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=5bffb9a1e https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=d6dd92d44 https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=b85217217 https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=924c2dd83 https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=36a786f7f https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=3674e4024 https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=eed3ac9f7 -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-09-08 8:23 ` Ihor Radchenko @ 2023-09-08 9:49 ` Bruno Barbier 0 siblings, 0 replies; 26+ messages in thread From: Bruno Barbier @ 2023-09-08 9:49 UTC (permalink / raw) To: Ihor Radchenko; +Cc: emacs-orgmode Ihor Radchenko <yantar92@posteo.net> writes: > Bruno Barbier <brubar.cs@gmail.com> writes: > >> From 86a5443948fc84a6a412ccf49d0c537608f465a7 Mon Sep 17 00:00:00 2001 >> From: Bruno BARBIER <brubar.cs@gmail.com> >> Date: Fri, 18 Nov 2022 20:14:20 +0100 >> Subject: [PATCH 1/7] ob-haskell: Add tests for GHCi >> ... > > Thanks! > Applied, onto main. > I also added a missing defvar declaration for > `inferior-haskell-root-dir'. Thanks Ihor! ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-19 9:12 [PATCH] Add tests for ob-haskell (GHCi) Bruno Barbier 2023-03-19 10:20 ` Ihor Radchenko @ 2023-03-23 10:35 ` Ihor Radchenko 2023-03-23 21:01 ` ParetoOptimalDev 1 sibling, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-03-23 10:35 UTC (permalink / raw) To: Bruno Barbier; +Cc: emacs-orgmode Bruno Barbier <brubar.cs@gmail.com> writes: > +(ert-deftest ob-haskell/let-multilines-1 () > + "Local definitions on multiple lines." > + :expected-result :failed > + (should (equal 6 (test-ob-haskell-ghci "" " > +:{ > + let { x=2 > + ; y=3 > + } > + in x*y > +:} > +")))) Part of the problem is that haskell-mode appears to use non-standard prompt: comint-prompt-regexp: ^[[:alnum:].*_() |λ]*> \|^λ?> Output: ghci| ghci| ghci| ghci| ghci| 6 Note "|". We may want to either report this upstream or modify `org-babel-interpret-haskell' further where it extends `comint-prompt-regexp'. > +(ert-deftest ob-haskell/ghci-info () > + "The ghci meta command ':info' ." > + (should (equal "repeat :: a -> [a] -- Defined in ‘GHC.List’" > + (test-ob-haskell-ghci ":results output" ":info repeat")))) On my system, the output contains different number of spaces in "[a]<spaces>-- ". It appears to be system-dependent. Looks like test's fault. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-23 10:35 ` Ihor Radchenko @ 2023-03-23 21:01 ` ParetoOptimalDev 2023-03-23 21:30 ` ParetoOptimalDev 2023-03-24 10:40 ` Ihor Radchenko 0 siblings, 2 replies; 26+ messages in thread From: ParetoOptimalDev @ 2023-03-23 21:01 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Bruno Barbier, emacs-orgmode Ihor Radchenko <yantar92@posteo.net> writes: > Part of the problem is that haskell-mode appears to use non-standard > prompt: > > comint-prompt-regexp: ^[[:alnum:].*_() |λ]*> \|^λ?> > Output: ghci| ghci| ghci| ghci| ghci| 6 > > Note "|". You can fix this by sending `set :prompt-cont ""`. Here is a demonstration: GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help ghci> ghci> :set prompt-cont "" ghci> :{ let { x = 2 ; y = 3 } in x*y :} 6 ghci> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-23 21:01 ` ParetoOptimalDev @ 2023-03-23 21:30 ` ParetoOptimalDev 2023-03-24 10:40 ` Ihor Radchenko 1 sibling, 0 replies; 26+ messages in thread From: ParetoOptimalDev @ 2023-03-23 21:30 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Bruno Barbier, emacs-orgmode I forgot to mention you can also set the prompt as well to something simple like `:set prompt "> "`. > Ihor Radchenko <yantar92@posteo.net> writes: > >> Part of the problem is that haskell-mode appears to use non-standard >> prompt: >> >> comint-prompt-regexp: ^[[:alnum:].*_() |λ]*> \|^λ?> >> Output: ghci| ghci| ghci| ghci| ghci| 6 >> >> Note "|". > > You can fix this by sending `set :prompt-cont ""`. Here is a > demonstration: > > GHCi, version 9.0.2: https://www.haskell.org/ghc/ :? for help > ghci> ghci> :set prompt-cont "" > ghci> :{ > let { x = 2 > ; y = 3 > } > in x*y > :} > 6 > ghci> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-23 21:01 ` ParetoOptimalDev 2023-03-23 21:30 ` ParetoOptimalDev @ 2023-03-24 10:40 ` Ihor Radchenko 2023-03-26 3:27 ` ParetoOptimalDev 1 sibling, 1 reply; 26+ messages in thread From: Ihor Radchenko @ 2023-03-24 10:40 UTC (permalink / raw) To: ParetoOptimalDev; +Cc: Bruno Barbier, emacs-orgmode ParetoOptimalDev <pareto.optimal@mailfence.com> writes: >> comint-prompt-regexp: ^[[:alnum:].*_() |λ]*> \|^λ?> >> Output: ghci| ghci| ghci| ghci| ghci| 6 >> >> Note "|". > > You can fix this by sending `set :prompt-cont ""`. Here is a > demonstration: I think you meant `:set prompt-cont ""`. It is a good idea, thanks! We use similar approach in ob-shell. Note that setting the prompt to non-"", makes little sense - we want to remove it anyway. Why do extra work? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 26+ messages in thread
* Re: [PATCH] Add tests for ob-haskell (GHCi) 2023-03-24 10:40 ` Ihor Radchenko @ 2023-03-26 3:27 ` ParetoOptimalDev 0 siblings, 0 replies; 26+ messages in thread From: ParetoOptimalDev @ 2023-03-26 3:27 UTC (permalink / raw) To: Ihor Radchenko; +Cc: Bruno Barbier, emacs-orgmode Ihor Radchenko <yantar92@posteo.net> writes: > Note that setting the prompt to non-"", makes little sense - we want to > remove it anyway. Why do extra work? Oh, I didn't notice you set the prompt to "". I thought the extra work you were being done was *because* somewhere else the default was set to "λ ". I agree, do whatever is less work and more normalized here. ^ permalink raw reply [flat|nested] 26+ messages in thread
end of thread, other threads:[~2023-09-08 9:50 UTC | newest] Thread overview: 26+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-03-19 9:12 [PATCH] Add tests for ob-haskell (GHCi) Bruno Barbier 2023-03-19 10:20 ` Ihor Radchenko 2023-03-19 10:28 ` Ihor Radchenko 2023-03-19 10:32 ` Bruno Barbier 2023-03-22 10:16 ` Ihor Radchenko 2023-03-24 10:36 ` Ihor Radchenko 2023-03-25 10:01 ` Bruno Barbier 2023-03-26 9:09 ` Ihor Radchenko 2023-03-26 9:40 ` Bruno Barbier 2023-03-26 9:46 ` Ihor Radchenko [not found] ` <notmuch-sha1-0807e1720f829950d42ef560bc30e56bd152766c> 2023-05-07 8:50 ` Bruno Barbier 2023-05-07 9:18 ` Ruijie Yu via General discussions about Org-mode. 2023-05-07 11:15 ` Bruno Barbier 2023-05-08 10:59 ` Ihor Radchenko 2023-05-21 7:40 ` Bruno Barbier 2023-06-02 8:44 ` Ihor Radchenko 2023-08-10 12:51 ` Ihor Radchenko 2023-08-25 19:10 ` Bruno Barbier 2023-09-07 14:21 ` Bruno Barbier 2023-09-08 8:23 ` Ihor Radchenko 2023-09-08 9:49 ` Bruno Barbier 2023-03-23 10:35 ` Ihor Radchenko 2023-03-23 21:01 ` ParetoOptimalDev 2023-03-23 21:30 ` ParetoOptimalDev 2023-03-24 10:40 ` Ihor Radchenko 2023-03-26 3:27 ` ParetoOptimalDev
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs/org-mode.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).