emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/pgtk 32b9b22: Merge remote-tracking branch 'origin/master' into


From: Po Lu
Subject: feature/pgtk 32b9b22: Merge remote-tracking branch 'origin/master' into feature/pgtk
Date: Thu, 16 Dec 2021 04:58:18 -0500 (EST)

branch: feature/pgtk
commit 32b9b22f66b1afcc614d5f76860d56d5630c5bc4
Merge: 81d2e84 0b43e7a
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Merge remote-tracking branch 'origin/master' into feature/pgtk
---
 doc/lispref/elisp.texi                     |   5 +
 doc/lispref/variables.texi                 | 155 ++++++++++
 etc/NEWS                                   |   6 +
 lisp/emacs-lisp/ert.el                     | 134 +++++----
 lisp/emacs-lisp/multisession.el            | 439 +++++++++++++++++++++++++++++
 lisp/international/emoji.el                |  20 +-
 lisp/outline.el                            |   5 +-
 lisp/progmodes/hideif.el                   |  40 ++-
 lisp/ses.el                                |  18 +-
 lisp/textmodes/reftex-vars.el              |  16 +-
 lisp/vc/ediff-util.el                      |  20 +-
 src/comp.c                                 |   6 +-
 src/dynlib.c                               |  12 +
 src/dynlib.h                               |   1 +
 src/pdumper.c                              |   2 +-
 src/xwidget.c                              |   2 +
 test/lisp/emacs-lisp/multisession-tests.el | 201 +++++++++++++
 test/src/sqlite-tests.el                   |   1 +
 18 files changed, 977 insertions(+), 106 deletions(-)

diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index b773ba8..2186203 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -526,6 +526,7 @@ Variables
 * Variables with Restricted Values::  Non-constant variables whose value can
                                         @emph{not} be an arbitrary Lisp object.
 * Generalized Variables::   Extending the concept of variables.
+* Multisession Variables::  Variables that survive restarting Emacs.
 
 Scoping Rules for Variable Bindings
 
@@ -547,6 +548,10 @@ Generalized Variables
 * Setting Generalized Variables::   The @code{setf} macro.
 * Adding Generalized Variables::    Defining new @code{setf} forms.
 
+Multisession Variables
+
+* Multisession Variables::      Variables that survive restarting Emacs.
+
 Functions
 
 * What Is a Function::      Lisp functions vs. primitives; terminology.
diff --git a/doc/lispref/variables.texi b/doc/lispref/variables.texi
index abef0b3..98a9487 100644
--- a/doc/lispref/variables.texi
+++ b/doc/lispref/variables.texi
@@ -44,6 +44,7 @@ representing the variable.
 * Variables with Restricted Values::  Non-constant variables whose value can
                                         @emph{not} be an arbitrary Lisp object.
 * Generalized Variables::       Extending the concept of variables.
+* Multisession Variables::      Variables that survive restarting Emacs.
 @end menu
 
 @node Global Variables
@@ -2752,3 +2753,157 @@ form that has not already had an appropriate expansion 
defined.  In
 Common Lisp, this is not an error since the function @code{(setf
 @var{func})} might be defined later.
 @end quotation
+
+@node Multisession Variables
+@section Multisession Variables
+
+@cindex multisession variable
+  When you set a variable to a value and then close Emacs and restart
+it, that value won't be automatically restored.  Users usually set
+normal variables in their startup files, or use Customize
+(@pxref{Customization}) to set user options permanently, and various
+packages have various files wher they store the data (e.g., Gnus
+stores this in @file{.newsrc.eld} and the URL library stores cookies
+in @file{~/.emacs.d/url/cookies}).
+
+For things in between these two extremes (i.e., configuration which
+goes in the startup file, and massive application state that goes into
+separate files), Emacs provides a facility to replicate data between
+sessions called @dfn{multisession variables}.  (This facility may not
+be available on all systems.)  To give you an idea of how these are
+meant to be used, here's a small example:
+
+@lisp
+@group
+(define-multisession-variable foo-var 0)
+(defun my-adder (num)
+  (interactive "nAdd number: ")
+  (setf (multisession-value foo)
+        (+ (multisession-value foo) num))
+  (message "The new number is: %s" (multisession-value foo)))
+@end group
+@end lisp
+
+@noindent
+This defines the variable @code{foo-var} and binds it to a special
+multisession object which is initialized with the value @samp{0} (if
+the variable doesn't already exist from a previous session).  The
+@code{my-adder} command queries the user for a number, adds this to
+the old (possibly saved value), and then saves the new value.
+
+This facility isn't meant to be used for huge data structures, but
+should be performant for most values.
+
+@defmac define-multisession-variable name initial-value &optional doc &rest 
args
+This macro defines @var{name} as a multisession variable, and gives it
+the @var{initial-value} if this variable hasn't been assigned a value
+earlier.  @var{doc} is the doc string, and several keyword arguments can
+be used in @var{args}:
+
+@table @code
+@item :package @var{package-symbol}
+This keyword says that a multisession variable belongs to the package
+specified by @var{package-symbol}.  The combination of
+@var{package-symbol} and @var{name} has to be unique.  If
+@var{package-symbol} isn't given, this will default to the first
+``segment'' of the @var{name} symbol's name, which is the part of its
+name up to and excluding the first @samp{-}.  For instance, if
+@var{name} is @code{foo-var} and @var{package-symbol} isn't given,
+@var{package-symbol} will default to @code{foo}.
+
+@cindex synchronized multisession variables
+@item :synchronized @var{bool}
+Multisession variables can be @dfn{synchronized} if @var{bool} is
+non-@code{nil}.  This means that if there're two concurrent Emacs
+instances running, and the other Emacs changes the multisession
+variable @code{foo-var}, the current Emacs instance will retrieve that
+modified data when accessing the value.  If @var{synchronized} is
+@code{nil} or missing, this won't happen, and the values in all
+Emacs sessions using the variable will be independent of each other.
+
+@item :storage @var{storage}
+Use the specified @var{storage} method.  This can be either
+@code{sqlite} (in Emacs compiled with SQLite support) or @code{files}.
+If not given, this defaults to the value of the
+@code{multisession-storage} variable, described below.
+@end table
+@end defmac
+
+@defun multisession-value variable
+This function returns the current value of @var{variable}.  If this
+variable hasn't been accessed before in this Emacs session, or if it's
+changed externally, it will be read in from external storage.  If not,
+the current value in this session is returned as is.  It is an error
+to call this function for a @var{variable} that is not a multisession
+variable.
+
+Values retrieved via @code{multisession-value} may or may not be
+@code{eq} to each other, but they will always be @code{equal}.
+
+This is a generalized variable (@pxref{Generalized Variables}), so the
+way to update such a variable is to say, for instance:
+
+@lisp
+(setf (multisession-value foo-bar) 'zot)
+@end lisp
+
+Only Emacs Lisp values that have a readable print syntax
+(@pxref{Printed Representation}) can be saved this way.
+
+If the multisession variable is synchronized, setting it may update
+the value first.  For instance:
+
+@lisp
+(cl-incf (multisession-value foo-bar))
+@end lisp
+
+This first checks whether the value has changed in a different
+Emacs instance, retrieves that value, and then adds 1 to that value and
+stores it.  But note that this is done without locking, so if many
+instances are updating the value at the same time, it's unpredictable
+which instance ``wins''.
+@end defun
+
+@defun multisession-delete object
+This function deletes @var{object} and its value from its persistent
+storage.
+@end defun
+
+@c FIXME: this lacks the documentation of the form of the arguments.
+@defun make-multisession
+You can also make persistent values that aren't tied to a specific
+variable, but are tied to an explicit package and key.
+
+@example
+(setq foo (make-multisession :package "mail"
+                             :key "friends"))
+(setf (multisession-value foo) 'everybody)
+@end example
+
+This supports the same keywords as
+@code{define-multisession-variable}, but also supports a
+@code{:initial-value} keyword, which specifies the default value.
+@end defun
+
+@defopt multisession-storage
+This variable controls how the multisession variables are stored.  It
+value defaults to @code{files}, which means that the values are stored
+in a one-file-per-variable structure inside the directory specified by
+@code{multisession-directory}.  If this value is @code{sqlite}
+instead, the values are stored in an SQLite database; this is only
+available if Emacs was built with SQLite support.
+@end defopt
+
+@defopt multisession-directory
+The multisession variables are stored under this directory, which
+defaults to @file{multisession/} subdirectory of the
+@code{user-emacs-directory}, which is typically
+@file{~/.emacs.d/multisession/}.
+@end defopt
+
+@findex multisession-edit-mode
+@deffn Command list-multisession-values
+This command pops up a buffer listing all the multisession variables,
+and enters a special mode @code{multisession-edit-mode} which allows
+you to delete them and edit their values.
+@end deffn
diff --git a/etc/NEWS b/etc/NEWS
index 8d83b2a..1d78f1f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -840,6 +840,12 @@ This change is now applied in 'dired-insert-directory'.
 * Lisp Changes in Emacs 29.1
 
 +++
+** New facility for handling session state: 'multisession-value'.
+This can be used as a convenient way to store (simple) application
+state, and 'M-x list-multisession-values' allows users to list
+(and edit) this data.
+
++++
 ** New function 'get-display-property'.
 This is like 'get-text-property', but works on the 'display' text
 property.
diff --git a/lisp/emacs-lisp/ert.el b/lisp/emacs-lisp/ert.el
index 981e239..597044c 100644
--- a/lisp/emacs-lisp/ert.el
+++ b/lisp/emacs-lisp/ert.el
@@ -1529,26 +1529,29 @@ the tests)."
 
 (defun ert-write-junit-test-report (stats)
   "Write a JUnit test report, generated from STATS."
-  ;; 
https://www.ibm.com/docs/de/developer-for-zos/14.1.0?topic=formats-junit-xml-format
+  ;; 
https://www.ibm.com/docs/en/developer-for-zos/14.1.0?topic=formats-junit-xml-format
   ;; https://llg.cubic.org/docs/junit/
   (unless (zerop (length (ert--stats-tests stats)))
     (when-let ((test-file
                 (symbol-file
-                 (ert-test-name (aref (ert--stats-tests stats) 0)) 
'ert--test)))
-      (with-temp-file (file-name-with-extension test-file "xml")
+                 (ert-test-name (aref (ert--stats-tests stats) 0)) 'ert--test))
+               (test-report (file-name-with-extension test-file "xml")))
+      (with-temp-file test-report
         (insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
-        (insert (format "<testsuites name=\"%s\" tests=\"%s\" failures=\"%s\" 
skipped=\"%s\" time=\"%s\">\n"
-                        (file-name-nondirectory test-file)
+        (insert (format "<testsuites name=\"%s\" tests=\"%s\" errors=\"%s\" 
failures=\"%s\" skipped=\"%s\" time=\"%s\">\n"
+                        (file-name-nondirectory test-report)
                         (ert-stats-total stats)
+                        (if (ert--stats-aborted-p stats) 1 0)
                         (ert-stats-completed-unexpected stats)
                         (ert-stats-skipped stats)
                         (float-time
                          (time-subtract
                           (ert--stats-end-time stats)
                           (ert--stats-start-time stats)))))
-        (insert (format "  <testsuite id=\"0\" name=\"%s\" tests=\"%s\" 
failures=\"%s\" skipped=\"%s\" time=\"%s\" timestamp=\"%s\">\n"
-                        (file-name-nondirectory test-file)
+        (insert (format "  <testsuite id=\"0\" name=\"%s\" tests=\"%s\" 
errors=\"%s\" failures=\"%s\" skipped=\"%s\" time=\"%s\" timestamp=\"%s\">\n"
+                        (file-name-nondirectory test-report)
                         (ert-stats-total stats)
+                        (if (ert--stats-aborted-p stats) 1 0)
                         (ert-stats-completed-unexpected stats)
                         (ert-stats-skipped stats)
                         (float-time
@@ -1570,40 +1573,52 @@ the tests)."
                                   (ert-test-result-expected-p test result))
                                  (ert-test-result-duration result)))
                  (if (and (ert-test-result-expected-p test result)
+                          (not (ert-test-aborted-with-non-local-exit-p result))
                           (not (ert-test-skipped-p result))
                           (zerop (length (ert-test-result-messages result))))
                      (insert "/>\n")
                    (insert ">\n")
-                   (if (ert-test-skipped-p result)
-                       (insert (format "      <skipped message=\"%s\" 
type=\"%s\">\n"
-                                       (xml-escape-string
-                                        (string-trim
-                                         (ert-reason-for-test-result result)))
-                                       (ert-string-for-test-result
-                                        result
-                                        (ert-test-result-expected-p
-                                         test result)))
-                               (xml-escape-string
-                                (string-trim
-                                 (ert-reason-for-test-result result)))
-                               "\n"
-                               "      </skipped>\n")
-                     (unless
-                         (ert-test-result-type-p
-                          result (ert-test-expected-result-type test))
-                       (insert (format "      <failure message=\"%s\" 
type=\"%s\">\n"
-                                       (xml-escape-string
-                                        (string-trim
-                                         (ert-reason-for-test-result result)))
-                                       (ert-string-for-test-result
-                                        result
-                                        (ert-test-result-expected-p
-                                         test result)))
-                               (xml-escape-string
-                                (string-trim
-                                 (ert-reason-for-test-result result)))
-                               "\n"
-                               "      </failure>\n")))
+                   (cond
+                    ((ert-test-skipped-p result)
+                     (insert (format "      <skipped message=\"%s\" 
type=\"%s\">\n"
+                                     (xml-escape-string
+                                      (string-trim
+                                       (ert-reason-for-test-result result)))
+                                     (ert-string-for-test-result
+                                      result
+                                      (ert-test-result-expected-p
+                                       test result)))
+                             (xml-escape-string
+                              (string-trim
+                               (ert-reason-for-test-result result)))
+                             "\n"
+                             "      </skipped>\n"))
+                    ((ert-test-aborted-with-non-local-exit-p result)
+                     (insert (format "      <error message=\"%s\" 
type=\"%s\">\n"
+                                     (file-name-nondirectory test-report)
+                                     (ert-string-for-test-result
+                                      result
+                                      (ert-test-result-expected-p
+                                       test result)))
+                             (format "Test %s aborted with non-local exit\n"
+                                     (xml-escape-string
+                                      (symbol-name (ert-test-name test))))
+                             "      </error>\n"))
+                    ((not (ert-test-result-type-p
+                           result (ert-test-expected-result-type test)))
+                     (insert (format "      <failure message=\"%s\" 
type=\"%s\">\n"
+                                     (xml-escape-string
+                                      (string-trim
+                                       (ert-reason-for-test-result result)))
+                                     (ert-string-for-test-result
+                                      result
+                                      (ert-test-result-expected-p
+                                       test result)))
+                             (xml-escape-string
+                              (string-trim
+                               (ert-reason-for-test-result result)))
+                             "\n"
+                             "      </failure>\n")))
                    (unless (zerop (length (ert-test-result-messages result)))
                      (insert "      <system-out>\n"
                              (xml-escape-string
@@ -1617,21 +1632,41 @@ the tests)."
   "Write a JUnit summary test report, generated from LOGFILES."
   (let ((report (file-name-with-extension
                  (getenv "EMACS_TEST_JUNIT_REPORT") "xml"))
-        (tests 0) (failures 0) (skipped 0) (time 0) (id 0))
+        (tests 0) (errors 0) (failures 0) (skipped 0) (time 0) (id 0))
     (with-temp-file report
       (dolist (logfile logfiles)
-        (let ((test-file (file-name-with-extension logfile "xml")))
-          (when (file-readable-p test-file)
-            (insert-file-contents-literally test-file)
+        (let ((test-report (file-name-with-extension logfile "xml")))
+          (if (not (file-readable-p test-report))
+              (let ((logfile (file-name-with-extension logfile "log")))
+                (insert (format "  <testsuite id=\"%s\" name=\"%s\" 
tests=\"1\" errors=\"1\" failures=\"0\" skipped=\"0\" time=\"0\" 
timestamp=\"%s\">\n"
+                                id test-report
+                                (ert--format-time-iso8601 (current-time))))
+                (insert (format "    <testcase name=\"Test report missing %s\" 
status=\"error\" time=\"0\">\n"
+                                (file-name-nondirectory test-report)))
+                (insert (format "      <error message=\"Test report missing 
%s\" type=\"error\">\n"
+                                (file-name-nondirectory test-report)))
+                (when (file-readable-p logfile)
+                  (insert (xml-escape-string
+                           (with-temp-buffer
+                             (insert-file-contents-literally logfile)
+                             (buffer-string)))))
+                (insert "      </error>\n"
+                        "    </testcase>\n"
+                        "  </testsuite>\n")
+                (cl-incf errors 1)
+                (cl-incf id 1))
+
+            (insert-file-contents-literally test-report)
             (when (looking-at-p
                    (regexp-quote "<?xml version=\"1.0\" encoding=\"utf-8\"?>"))
               (delete-region (point) (line-beginning-position 2)))
             (when (looking-at
-                   "<testsuites name=\".+\" tests=\"\\(.+\\)\" 
failures=\"\\(.+\\)\" skipped=\"\\(.+\\)\" time=\"\\(.+\\)\">")
+                   "<testsuites name=\".+\" tests=\"\\(.+\\)\" 
errors=\"\\(.+\\)\" failures=\"\\(.+\\)\" skipped=\"\\(.+\\)\" 
time=\"\\(.+\\)\">")
               (cl-incf tests (string-to-number (match-string 1)))
-              (cl-incf failures  (string-to-number (match-string 2)))
-              (cl-incf skipped (string-to-number (match-string 3)))
-              (cl-incf time (string-to-number (match-string 4)))
+              (cl-incf errors  (string-to-number (match-string 2)))
+              (cl-incf failures  (string-to-number (match-string 3)))
+              (cl-incf skipped (string-to-number (match-string 4)))
+              (cl-incf time (string-to-number (match-string 5)))
               (delete-region (point) (line-beginning-position 2)))
             (when (looking-at "  <testsuite id=\"\\(0\\)\"")
               (replace-match (number-to-string id) nil nil nil 1)
@@ -1639,16 +1674,17 @@ the tests)."
             (goto-char (point-max))
             (beginning-of-line 0)
             (when (looking-at-p "</testsuites>")
-              (delete-region (point) (line-beginning-position 2)))
-            (narrow-to-region (point-max) (point-max)))))
+              (delete-region (point) (line-beginning-position 2))))
+
+          (narrow-to-region (point-max) (point-max))))
 
       (insert "</testsuites>\n")
       (widen)
       (goto-char (point-min))
       (insert "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
-      (insert (format "<testsuites name=\"%s\" tests=\"%s\" failures=\"%s\" 
skipped=\"%s\" time=\"%s\">\n"
+      (insert (format "<testsuites name=\"%s\" tests=\"%s\" errors=\"%s\" 
failures=\"%s\" skipped=\"%s\" time=\"%s\">\n"
                       (file-name-nondirectory report)
-                      tests failures skipped time)))))
+                      tests errors failures skipped time)))))
 
 (defun ert-summarize-tests-batch-and-exit (&optional high)
   "Summarize the results of testing.
diff --git a/lisp/emacs-lisp/multisession.el b/lisp/emacs-lisp/multisession.el
new file mode 100644
index 0000000..17c9384
--- /dev/null
+++ b/lisp/emacs-lisp/multisession.el
@@ -0,0 +1,439 @@
+;;; multisession.el --- Multisession storage for variables  -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'eieio)
+(require 'sqlite)
+(require 'url)
+(require 'tabulated-list)
+
+(defcustom multisession-storage 'files
+  "Storage method for multisession variables.
+Valid methods are `sqlite' and `files'."
+  :type '(choice (const :tag "SQLite" sqlite)
+                 (const :tag "Files" files))
+  :version "29.1"
+  :group 'files)
+
+(defcustom multisession-directory (expand-file-name "multisession/"
+                                                    user-emacs-directory)
+  "Directory to store multisession variables."
+  :type 'file
+  :version "29.1"
+  :group 'files)
+
+;;;###autoload
+(defmacro define-multisession-variable (name initial-value &optional doc
+                                             &rest args)
+  "Make NAME into a multisession variable initialized from INITIAL-VALUE.
+DOC should be a doc string, and ARGS are keywords as applicable to
+`make-multisession'."
+  (declare (indent defun))
+  (unless (plist-get args :package)
+    (setq args (nconc (list :package
+                            (replace-regexp-in-string "-.*" ""
+                                                      (symbol-name name)))
+                      args)))
+  `(defvar ,name
+     (make-multisession :key ,(symbol-name name)
+                        :initial-value ,initial-value
+                        ,@args)
+     ,@(list doc)))
+
+(defconst multisession--unbound (make-symbol "unbound"))
+
+(cl-defstruct (multisession
+               (:constructor nil)
+               (:constructor multisession--create)
+               (:conc-name multisession--))
+  "A persistent variable that will live across Emacs invocations."
+  key
+  (initial-value nil)
+  package
+  (storage multisession-storage)
+  (synchronized nil)
+  (cached-value multisession--unbound)
+  (cached-sequence 0))
+
+(cl-defun make-multisession (&key key initial-value package synchronized
+                                  storage)
+  "Create a multisession object."
+  (unless package
+    (error "No package for the multisession object"))
+  (unless key
+    (error "No key for the multisession object"))
+  (unless (stringp package)
+    (error "The package has to be a string"))
+  (unless (stringp key)
+    (error "The key has to be a string"))
+  (multisession--create
+   :key key
+   :synchronized synchronized
+   :initial-value initial-value
+   :package package
+   :storage (or storage multisession-storage)))
+
+(defun multisession-value (object)
+  "Return the value of the multisession OBJECT."
+  (if (null user-init-file)
+      ;; If we don't have storage, then just return the value from the
+      ;; object.
+      (if (eq (multisession--cached-value object) multisession--unbound)
+          (multisession--initial-value object)
+        (multisession--cached-value object))
+    ;; We have storage, so we update from storage.
+    (multisession-backend-value (multisession--storage object) object)))
+
+(defun multisession--set-value (object value)
+  "Set the stored value of OBJECT to VALUE."
+  (if (null user-init-file)
+      ;; We have no backend, so just store the value.
+      (setf (multisession--cached-value object) value)
+    ;; We have a backend.
+    (multisession--backend-set-value (multisession--storage object)
+                                     object value)))
+
+(defun multisession-delete (object)
+  "Delete OBJECT from the backend storage."
+  (multisession--backend-delete (multisession--storage object) object))
+
+(gv-define-simple-setter multisession-value multisession--set-value)
+
+;; SQLite Backend
+
+(declare-function sqlite-execute "sqlite.c")
+(declare-function sqlite-select "sqlite.c")
+(declare-function sqlite-open "sqlite.c")
+(declare-function sqlite-pragma "sqlite.c")
+(declare-function sqlite-transaction "sqlite.c")
+(declare-function sqlite-commit "sqlite.c")
+
+(defvar multisession--db nil)
+
+(defun multisession--ensure-db ()
+  (unless multisession--db
+    (let* ((file (expand-file-name "sqlite/multisession.sqlite"
+                                   multisession-directory))
+           (dir (file-name-directory file)))
+      (unless (file-exists-p dir)
+        (make-directory dir t))
+      (setq multisession--db (sqlite-open file)))
+    (with-sqlite-transaction multisession--db
+      ;; Use a write-ahead-log (available since 2010), which makes
+      ;; writes a lot faster.
+      (sqlite-pragma multisession--db "journal_mode = WAL")
+      (sqlite-pragma multisession--db "synchronous = NORMAL")
+      (unless (sqlite-select
+               multisession--db
+               "select name from sqlite_master where type = 'table' and name = 
'multisession'")
+        ;; Tidy up the database automatically.
+        (sqlite-pragma multisession--db "auto_vacuum = FULL")
+        ;; Create the table.
+        (sqlite-execute
+         multisession--db
+         "create table multisession (package text not null, key text not null, 
sequence number not null default 1, value text not null)")
+        (sqlite-execute
+         multisession--db
+         "create unique index multisession_idx on multisession (package, 
key)")))))
+
+(cl-defmethod multisession-backend-value ((_type (eql sqlite)) object)
+  (multisession--ensure-db)
+  (let ((id (list (multisession--package object)
+                  (multisession--key object))))
+    (cond
+     ;; We have no value yet; check the database.
+     ((eq (multisession--cached-value object) multisession--unbound)
+      (let ((stored
+             (car
+              (sqlite-select
+               multisession--db
+               "select value, sequence from multisession where package = ? and 
key = ?"
+               id))))
+        (if stored
+            (let ((value (car (read-from-string (car stored)))))
+              (setf (multisession--cached-value object) value
+                    (multisession--cached-sequence object) (cadr stored))
+              value)
+          ;; Nothing; return the initial value.
+          (multisession--initial-value object))))
+     ;; We have a value, but we want to update in case some other
+     ;; Emacs instance has updated.
+     ((multisession--synchronized object)
+      (let ((stored
+             (car
+              (sqlite-select
+               multisession--db
+               "select value, sequence from multisession where sequence > ? 
and package = ? and key = ?"
+               (cons (multisession--cached-sequence object) id)))))
+        (if stored
+            (let ((value (car (read-from-string (car stored)))))
+              (setf (multisession--cached-value object) value
+                    (multisession--cached-sequence object) (cadr stored))
+              value)
+          ;; Nothing, return the cached value.
+          (multisession--cached-value object))))
+     ;; Just return the cached value.
+     (t
+      (multisession--cached-value object)))))
+
+(cl-defmethod multisession--backend-set-value ((_type (eql sqlite))
+                                               object value)
+  (catch 'done
+    (let ((i 0))
+      (while (< i 10)
+        (condition-case nil
+            (throw 'done (multisession--set-value-sqlite object value))
+          (sqlite-locked-error
+           (setq i (1+ i))
+           (sleep-for (+ 0.1 (/ (float (random 10)) 10))))))
+      (signal 'sqlite-locked-error "Database is locked"))))
+
+(defun multisession--set-value-sqlite (object value)
+  (multisession--ensure-db)
+  (with-sqlite-transaction multisession--db
+    (let ((id (list (multisession--package object)
+                    (multisession--key object)))
+          (pvalue
+           (let ((print-length nil)
+                 (print-circle t)
+                 (print-level nil))
+             (prin1-to-string value))))
+      (condition-case nil
+          (ignore (read-from-string pvalue))
+        (error (error "Unable to store unreadable value: %s" pvalue)))
+      (sqlite-execute
+       multisession--db
+       "insert into multisession(package, key, sequence, value) values(?, ?, 
1, ?) on conflict(package, key) do update set sequence = sequence + 1, value = 
?"
+       (append id (list pvalue pvalue)))
+      (setf (multisession--cached-sequence object)
+            (caar (sqlite-select
+                   multisession--db
+                   "select sequence from multisession where package = ? and 
key = ?"
+                   id)))
+      (setf (multisession--cached-value object) value))))
+
+(cl-defmethod multisession--backend-values ((_type (eql sqlite)))
+  (multisession--ensure-db)
+  (sqlite-select
+   multisession--db
+   "select package, key, value from multisession order by package, key"))
+
+(cl-defmethod multisession--backend-delete ((_type (eql sqlite)) object)
+  (sqlite-execute multisession--db
+                  "delete from multisession where package = ? and key = ?"
+                  (list (multisession--package object)
+                        (multisession--key object))))
+
+;; Files Backend
+
+(defun multisession--encode-file-name (name)
+  (url-hexify-string name))
+
+(defun multisession--update-file-value (file object)
+  (condition-case nil
+      (with-temp-buffer
+        (let* ((time (file-attribute-modification-time
+                      (file-attributes file)))
+               (coding-system-for-read 'utf-8))
+          (insert-file-contents file)
+          (let ((stored (read (current-buffer))))
+            (setf (multisession--cached-value object) stored
+                  (multisession--cached-sequence object) time)
+            stored)))
+    ;; If the file is contended (could happen with file locking in
+    ;; Windws) or unreadable, just return the current value.
+    (error
+     (if (eq (multisession--cached-value object) multisession--unbound)
+         (multisession--initial-value object)
+       (multisession--cached-value object)))))
+
+(defun multisession--object-file-name (object)
+  (expand-file-name
+   (concat "files/"
+           (multisession--encode-file-name (multisession--package object))
+           "/"
+           (multisession--encode-file-name (multisession--key object))
+           ".value")
+   multisession-directory))
+
+(cl-defmethod multisession-backend-value ((_type (eql files)) object)
+  (let ((file (multisession--object-file-name object)))
+    (cond
+     ;; We have no value yet; see whether it's stored.
+     ((eq (multisession--cached-value object) multisession--unbound)
+      (if (file-exists-p file)
+          (multisession--update-file-value file object)
+        ;; Nope; return the initial value.
+        (multisession--initial-value object)))
+     ;; We have a value, but we want to update in case some other
+     ;; Emacs instance has updated.
+     ((multisession--synchronized object)
+      (if (and (file-exists-p file)
+               (time-less-p (multisession--cached-sequence object)
+                            (file-attribute-modification-time
+                             (file-attributes file))))
+          (multisession--update-file-value file object)
+        ;; Nothing, return the cached value.
+        (multisession--cached-value object)))
+     ;; Just return the cached value.
+     (t
+      (multisession--cached-value object)))))
+
+(cl-defmethod multisession--backend-set-value ((_type (eql files))
+                                               object value)
+  (let ((file (multisession--object-file-name object))
+        (time (current-time)))
+    ;; Ensure that the directory exists.
+    (let ((dir (file-name-directory file)))
+      (unless (file-exists-p dir)
+        (make-directory dir t)))
+    (with-temp-buffer
+      (let ((print-length nil)
+            (print-circle t)
+            (print-level nil))
+        (prin1 value (current-buffer)))
+      (goto-char (point-min))
+      (condition-case nil
+          (read (current-buffer))
+        (error (error "Unable to store unreadable value: %s" (buffer-string))))
+      ;; Write to a temp file in the same directory and rename to the
+      ;; file for somewhat better atomicity.
+      (let ((coding-system-for-write 'utf-8)
+            (create-lockfiles nil)
+            (temp (make-temp-name file)))
+        (write-region (point-min) (point-max) temp nil 'silent)
+        (set-file-times temp time)
+        (rename-file temp file t)))
+    (setf (multisession--cached-sequence object) time
+          (multisession--cached-value object) value)))
+
+(cl-defmethod multisession--backend-values ((_type (eql files)))
+  (mapcar (lambda (file)
+            (let ((bits (file-name-split file)))
+              (list (url-unhex-string (car (last bits 2)))
+                    (url-unhex-string
+                     (file-name-sans-extension (car (last bits))))
+                    (with-temp-buffer
+                      (let ((coding-system-for-read 'utf-8))
+                        (insert-file-contents file)
+                        (read (current-buffer)))))))
+          (directory-files-recursively
+           (expand-file-name "files" multisession-directory)
+           "\\.value\\'")))
+
+(cl-defmethod multisession--backend-delete ((_type (eql files)) object)
+  (let ((file (multisession--object-file-name object)))
+    (when (file-exists-p file)
+      (delete-file file))))
+
+;; Mode for editing.
+
+(defvar-keymap multisession-edit-mode-map
+  :parent tabulated-list-mode-map
+  "d" #'multisession-delete-value
+  "e" #'multisession-edit-value)
+
+(define-derived-mode multisession-edit-mode special-mode "Multisession"
+  "This mode lists all elements in the \"multisession\" database."
+  :interactive nil
+  (buffer-disable-undo)
+  (setq-local buffer-read-only t
+              truncate-lines t)
+  (setq tabulated-list-format
+        [("Package" 10)
+         ("Key" 30)
+         ("Value" 30)])
+  (setq-local revert-buffer-function #'multisession-edit-mode--revert))
+
+;;;###autoload
+(defun list-multisession-values (&optional choose-storage)
+  "List all values in the \"multisession\" database.
+If CHOOSE-STORAGE (interactively, the prefix), query for the
+storage method to list."
+  (interactive "P")
+  (let ((storage
+         (if choose-storage
+             (intern (completing-read "Storage method: " '(sqlite files) nil 
t))
+           multisession-storage)))
+    (pop-to-buffer (get-buffer-create (format "*Multisession %s*" storage)))
+    (multisession-edit-mode)
+    (setq-local multisession-storage storage)
+    (multisession-edit-mode--revert)
+    (goto-char (point-min))))
+
+(defun multisession-edit-mode--revert (&rest _)
+  (let ((inhibit-read-only t)
+        (id (get-text-property (point) 'tabulated-list-id)))
+    (erase-buffer)
+    (tabulated-list-init-header)
+    (setq tabulated-list-entries
+          (mapcar (lambda (elem)
+                    (list
+                     (cons (car elem) (cadr elem))
+                     (vector (car elem) (cadr elem)
+                             (string-replace "\n" "\\n"
+                                             (format "%s" (caddr elem))))))
+                  (multisession--backend-values multisession-storage)))
+    (tabulated-list-print t)
+    (goto-char (point-min))
+    (when id
+      (when-let ((match
+                  (text-property-search-forward 'tabulated-list-id id t)))
+        (goto-char (prop-match-beginning match))))))
+
+(defun multisession-delete-value (id)
+  "Delete the value at point."
+  (interactive (list (get-text-property (point) 'tabulated-list-id))
+               multisession-edit-mode)
+  (unless id
+    (error "No value on the current line"))
+  (unless (yes-or-no-p "Really delete this item? ")
+    (user-error "Not deleting"))
+  (multisession--backend-delete multisession-storage
+                                (make-multisession :package (car id)
+                                                   :key (cdr id)))
+  (let ((inhibit-read-only t))
+    (beginning-of-line)
+    (delete-region (point) (progn (forward-line 1) (point)))))
+
+(defun multisession-edit-value (id)
+  "Edit the value at point."
+  (interactive (list (get-text-property (point) 'tabulated-list-id))
+               multisession-edit-mode)
+  (unless id
+    (error "No value on the current line"))
+  (let* ((object (make-multisession
+                  :package (car id)
+                  :key (cdr id)
+                  :storage multisession-storage))
+         (value (multisession-value object)))
+    (setf (multisession-value object)
+          (car (read-from-string
+                (read-string "New value: " (prin1-to-string value))))))
+  (multisession-edit-mode--revert))
+
+(provide 'multisession)
+
+;;; multisession.el ends here
diff --git a/lisp/international/emoji.el b/lisp/international/emoji.el
index 5f8c358..a4dec97 100644
--- a/lisp/international/emoji.el
+++ b/lisp/international/emoji.el
@@ -30,6 +30,7 @@
 (require 'cl-lib)
 (require 'cl-extra)
 (require 'transient)
+(require 'multisession)
 
 (defgroup emoji nil
   "Inserting Emojis."
@@ -59,7 +60,7 @@
 (defvar emoji--derived nil)
 (defvar emoji--names (make-hash-table :test #'equal))
 (defvar emoji--done-derived nil)
-(defvar emoji--recent (list "😀" "😖"))
+(define-multisession-variable emoji--recent (list "😀" "😖"))
 (defvar emoji--insert-buffer)
 
 ;;;###autoload
@@ -83,7 +84,7 @@ of a visual interface."
   (unless (fboundp 'emoji--command-Emoji)
     (emoji--define-transient))
   (funcall (emoji--define-transient
-            (cons "Recent" emoji--recent) t)))
+            (cons "Recent" (multisession-value emoji--recent)) t)))
 
 ;;;###autoload
 (defun emoji-search ()
@@ -529,15 +530,18 @@ character) under point is."
   (lambda ()
     (interactive)
     (funcall (emoji--define-transient
-              (cons "Recent" emoji--recent) t end-function))))
+              (cons "Recent" (multisession-value emoji--recent))
+              t end-function))))
 
 (defun emoji--add-recent (glyph)
   "Add GLYPH to the set of recently used emojis."
-  (setq emoji--recent (delete glyph emoji--recent))
-  (push glyph emoji--recent)
-  ;; Shorten the list.
-  (when-let ((tail (nthcdr 30 emoji--recent)))
-    (setcdr tail nil)))
+  (let ((recent (multisession-value emoji--recent)))
+    (setq recent (delete glyph recent))
+    (push glyph recent)
+    ;; Shorten the list.
+    (when-let ((tail (nthcdr 30 recent)))
+      (setcdr tail nil))
+    (setf (multisession-value emoji--recent) recent)))
 
 (defun emoji--columnize (list columns)
   "Split LIST into COLUMN columns."
diff --git a/lisp/outline.el b/lisp/outline.el
index 2ede4e2..5e3d4e0 100644
--- a/lisp/outline.el
+++ b/lisp/outline.el
@@ -283,6 +283,7 @@ buffers (yet) -- that will be amended in a future version.
 The `outline-minor-mode-buttons' variable specifies how the
 buttons should look."
   :type 'boolean
+  :safe #'booleanp
   :version "29.1")
 
 (defcustom outline-minor-mode-buttons
@@ -376,8 +377,8 @@ When point is on a heading line, then typing `TAB' cycles 
between `hide all',
 a heading line cycles the whole buffer (`outline-cycle-buffer').
 Typing these keys anywhere outside heading lines uses their default bindings."
   :type 'boolean
+  :safe #'booleanp
   :version "28.1")
-;;;###autoload(put 'outline-minor-mode-cycle 'safe-local-variable 'booleanp)
 
 (defcustom outline-minor-mode-highlight nil
   "Highlight headings in `outline-minor-mode' using font-lock keywords.
@@ -391,8 +392,8 @@ faces to major mode's faces."
                  (const :tag "Overwrite major mode faces" override)
                  (const :tag "Append outline faces to major mode faces" append)
                  (const :tag "Highlight separately from major mode faces" t))
+  :safe #'symbolp
   :version "28.1")
-;;;###autoload(put 'outline-minor-mode-highlight 'safe-local-variable 'symbolp)
 
 (defun outline-minor-mode-highlight-buffer ()
   ;; Fallback to overlays when font-lock is unsupported.
diff --git a/lisp/progmodes/hideif.el b/lisp/progmodes/hideif.el
index 87732c1..538ec4d 100644
--- a/lisp/progmodes/hideif.el
+++ b/lisp/progmodes/hideif.el
@@ -181,30 +181,24 @@ Effective only if `hide-ifdef-expand-reinclusion-guard' 
is t."
   :type 'regexp
   :version "25.1")
 
-(defvar hide-ifdef-mode-submap
+(defvar-keymap hide-ifdef-mode-submap
+  :doc "Keymap used by `hide-ifdef-mode' under `hide-ifdef-mode-prefix-key'."
   ;; Set up the submap that goes after the prefix key.
-  (let ((map (make-sparse-keymap)))
-    (define-key map "d" 'hide-ifdef-define)
-    (define-key map "u" 'hide-ifdef-undef)
-    (define-key map "D" 'hide-ifdef-set-define-alist)
-    (define-key map "U" 'hide-ifdef-use-define-alist)
-
-    (define-key map "h" 'hide-ifdefs)
-    (define-key map "s" 'show-ifdefs)
-    (define-key map "\C-d" 'hide-ifdef-block)
-    (define-key map "\C-s" 'show-ifdef-block)
-    (define-key map "e" 'hif-evaluate-macro)
-    (define-key map "C" 'hif-clear-all-ifdef-defined)
-
-    (define-key map "\C-q" 'hide-ifdef-toggle-read-only)
-    (define-key map "\C-w" 'hide-ifdef-toggle-shadowing)
-    (substitute-key-definition
-     'read-only-mode 'hide-ifdef-toggle-outside-read-only map)
-    ;; `toggle-read-only' is obsoleted by `read-only-mode'.
-    (substitute-key-definition
-     'toggle-read-only 'hide-ifdef-toggle-outside-read-only map)
-    map)
-  "Keymap used by `hide-ifdef-mode' under `hide-ifdef-mode-prefix-key'.")
+  "d"   #'hide-ifdef-define
+  "u"   #'hide-ifdef-undef
+  "D"   #'hide-ifdef-set-define-alist
+  "U"   #'hide-ifdef-use-define-alist
+  "h"   #'hide-ifdefs
+  "s"   #'show-ifdefs
+  "C-d" #'hide-ifdef-block
+  "C-s" #'show-ifdef-block
+  "e"   #'hif-evaluate-macro
+  "C"   #'hif-clear-all-ifdef-defined
+  "C-q" #'hide-ifdef-toggle-read-only
+  "C-w" #'hide-ifdef-toggle-shadowing
+  "<remap> <read-only-mode>" #'hide-ifdef-toggle-outside-read-only
+  ;; `toggle-read-only' is obsoleted by `read-only-mode'.
+  "<remap> <toggle-read-only>" #'hide-ifdef-toggle-outside-read-only)
 
 (defcustom hide-ifdef-mode-prefix-key "\C-c@"
   "Prefix key for all Hide-Ifdef mode commands."
diff --git a/lisp/ses.el b/lisp/ses.el
index 5e2d254..8496aee 100644
--- a/lisp/ses.el
+++ b/lisp/ses.el
@@ -227,12 +227,6 @@ Used for listing local printers or renamed cells.")
                "w"       ses-set-column-width
                "x"       ses-export-keymap
                "\M-p"    ses-read-column-printer))
-       (repl '(;;We'll replace these wherever they appear in the keymap
-               clipboard-kill-region ses-kill-override
-               end-of-line           ses-end-of-line
-               kill-line             ses-delete-row
-               kill-region           ses-kill-override
-               open-line             ses-insert-row))
        (numeric "0123456789.-")
        (newmap (make-keymap)))
     ;;Get rid of printables
@@ -240,13 +234,11 @@ Used for listing local printers or renamed cells.")
     ;;These keys insert themselves as the beginning of a numeric value
     (dotimes (x (length numeric))
       (define-key newmap (substring numeric x (1+ x)) 'ses-read-cell))
-    ;;Override these global functions wherever they're bound
-    (while repl
-      (substitute-key-definition (car repl) (cadr repl) newmap
-                                (current-global-map))
-      (setq repl (cddr repl)))
-    ;;Apparently substitute-key-definition doesn't catch this?
-    (define-key newmap [(menu-bar) edit cut] 'ses-kill-override)
+    (define-key newmap [remap clipboard-kill-region] #'ses-kill-override)
+    (define-key newmap [remap end-of-line]           #'ses-end-of-line)
+    (define-key newmap [remap kill-line]             #'ses-delete-row)
+    (define-key newmap [remap kill-region]           #'ses-kill-override)
+    (define-key newmap [remap open-line]             #'ses-insert-row)
     ;;Define our other local keys
     (while keys
       (define-key newmap (car keys) (cadr keys))
diff --git a/lisp/textmodes/reftex-vars.el b/lisp/textmodes/reftex-vars.el
index eedc067..dedd746 100644
--- a/lisp/textmodes/reftex-vars.el
+++ b/lisp/textmodes/reftex-vars.el
@@ -70,12 +70,16 @@
       ("tabwindow" ?f nil nil 1)))
 
     (rotating    "Sidewaysfigure and table"
-     (("sidewaysfigure" ?f nil nil caption)
-      ("sidewaystable"  ?t nil nil caption)))
-
-    (sidecap      "CSfigure and SCtable"
-     (("SCfigure"       ?f nil nil caption)
-      ("SCtable"        ?t nil nil caption)))
+     (("sidewaysfigure"  ?f nil nil caption)
+      ("sidewaysfigure*" ?f nil nil caption)
+      ("sidewaystable"   ?t nil nil caption)
+      ("sidewaystable*"  ?t nil nil caption)))
+
+    (sidecap      "SCfigure and SCtable"
+     (("SCfigure"  ?f nil nil caption)
+      ("SCfigure*" ?f nil nil caption)
+      ("SCtable"   ?t nil nil caption)
+      ("SCtable*"  ?t nil nil caption)))
 
     (subfigure   "Subfigure environments/macro"
      (("subfigure"   ?f nil nil caption)
diff --git a/lisp/vc/ediff-util.el b/lisp/vc/ediff-util.el
index d4660a1..c2b08bd 100644
--- a/lisp/vc/ediff-util.el
+++ b/lisp/vc/ediff-util.el
@@ -521,7 +521,25 @@ to invocation.")
     (erase-buffer)
     (ediff-set-help-message)
     (insert ediff-help-message)
-    (shrink-window-if-larger-than-buffer)
+    ;; With the fix for Bug#49277 and an 'ediff-setup-windows-plain'
+    ;; layout, the window of the control buffer we want to adjust here
+    ;; is no longer the lower of two windows on their frame both showing
+    ;; that control buffer but rather the bottom-most window in the
+    ;; established ediff layout for that frame.  As a consequence,
+    ;; 'shrink-window-if-larger-than-buffer' will fail to show the whole
+    ;; buffer with 'ediff-toggle-help' because that window's maximum
+    ;; height is not half the height of its frame but the height of the
+    ;; control buffer's window in the established layout (Bug#52504).
+    ;;
+    ;; The form below is an attempt to emulate the behavior of Emacs 27
+    ;; as faithfully as possible in this regard (the use of 'ceiling'
+    ;; mimics the behavior of 'split-window' giving the lower window the
+    ;; residue line when the window to split has an uneven number of
+    ;; lines).
+    (when (and (window-combined-p)
+              (pos-visible-in-window-p (point-min)))
+      (fit-window-to-buffer
+       nil (ceiling (/ (window-total-height (frame-root-window)) 2.0))))
     (or (ediff-multiframe-setup-p)
        (ediff-indent-help-message))
     (ediff-set-help-overlays)
diff --git a/src/comp.c b/src/comp.c
index fb9b1a5..1fb3848 100644
--- a/src/comp.c
+++ b/src/comp.c
@@ -5278,16 +5278,16 @@ LATE_LOAD has to be non-nil when loading for deferred 
compilation.  */)
        Fmake_temp_file_internal (filename, Qnil, build_string (".eln.tmp"),
                                  Qnil);
       if (NILP (Ffile_writable_p (tmp_filename)))
-       comp_u->handle = dynlib_open (SSDATA (encoded_filename));
+       comp_u->handle = dynlib_open_for_eln (SSDATA (encoded_filename));
       else
        {
          Frename_file (filename, tmp_filename, Qt);
-         comp_u->handle = dynlib_open (SSDATA (ENCODE_FILE (tmp_filename)));
+         comp_u->handle = dynlib_open_for_eln (SSDATA (ENCODE_FILE 
(tmp_filename)));
          Frename_file (tmp_filename, filename, Qnil);
        }
     }
   else
-    comp_u->handle = dynlib_open (SSDATA (encoded_filename));
+    comp_u->handle = dynlib_open_for_eln (SSDATA (encoded_filename));
 
   if (!comp_u->handle)
     xsignal2 (Qnative_lisp_load_failed, filename,
diff --git a/src/dynlib.c b/src/dynlib.c
index a8c8843..e9a775f 100644
--- a/src/dynlib.c
+++ b/src/dynlib.c
@@ -104,6 +104,12 @@ dynlib_open (const char *dll_fname)
   return (dynlib_handle_ptr) hdll;
 }
 
+dynlib_handle_ptr
+dynlib_open_for_eln (const char *dll_fname)
+{
+  return dynlib_open (dll_fname);
+}
+
 void *
 dynlib_sym (dynlib_handle_ptr h, const char *sym)
 {
@@ -270,6 +276,12 @@ dynlib_close (dynlib_handle_ptr h)
 dynlib_handle_ptr
 dynlib_open (const char *path)
 {
+  return dlopen (path, RTLD_LAZY | RTLD_GLOBAL);
+}
+
+dynlib_handle_ptr
+dynlib_open_for_eln (const char *path)
+{
   return dlopen (path, RTLD_LAZY);
 }
 
diff --git a/src/dynlib.h b/src/dynlib.h
index e20d889..05ba798 100644
--- a/src/dynlib.h
+++ b/src/dynlib.h
@@ -24,6 +24,7 @@ along with GNU Emacs.  If not, see 
<https://www.gnu.org/licenses/>.  */
 
 typedef void *dynlib_handle_ptr;
 dynlib_handle_ptr dynlib_open (const char *path);
+dynlib_handle_ptr dynlib_open_for_eln (const char *path);
 int dynlib_close (dynlib_handle_ptr h);
 const char *dynlib_error (void);
 
diff --git a/src/pdumper.c b/src/pdumper.c
index 8f03684..554b530 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -5353,7 +5353,7 @@ dump_do_dump_relocation (const uintptr_t dump_base,
           their file names through expand-file-name and
           decode-coding-string.  */
        comp_u->file = eln_fname;
-       comp_u->handle = dynlib_open (SSDATA (eln_fname));
+       comp_u->handle = dynlib_open_for_eln (SSDATA (eln_fname));
        if (!comp_u->handle)
          {
            fprintf (stderr, "Error using execdir %s:\n",
diff --git a/src/xwidget.c b/src/xwidget.c
index bd64f48..63ac055 100644
--- a/src/xwidget.c
+++ b/src/xwidget.c
@@ -149,6 +149,8 @@ fails.  */)
   if (!EQ (type, Qwebkit))
     error ("Bad xwidget type");
 
+  Frequire (Qxwidget, Qnil, Qnil);
+
   struct xwidget *xw = allocate_xwidget ();
   Lisp_Object val;
   xw->type = type;
diff --git a/test/lisp/emacs-lisp/multisession-tests.el 
b/test/lisp/emacs-lisp/multisession-tests.el
new file mode 100644
index 0000000..41fcde0
--- /dev/null
+++ b/test/lisp/emacs-lisp/multisession-tests.el
@@ -0,0 +1,201 @@
+;;; multisession-tests.el --- Tests for multisession.el  -*- lexical-binding: 
t; -*-
+
+;; Copyright (C) 2021 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs 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.
+
+;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; Code:
+
+(require 'multisession)
+(require 'ert)
+(require 'ert-x)
+(require 'cl-lib)
+
+(ert-deftest multi-test-sqlite-simple ()
+  (skip-unless (sqlite-available-p))
+  (ert-with-temp-file dir
+    :directory t
+    (let ((user-init-file "/tmp/foo.el")
+          (multisession-storage 'sqlite)
+          (multisession-directory dir))
+      (unwind-protect
+          (progn
+            (define-multisession-variable foo 0
+              ""
+              :synchronized t)
+            (should (= (multisession-value foo) 0))
+            (cl-incf (multisession-value foo))
+            (should (= (multisession-value foo) 1))
+            (call-process
+             (concat invocation-directory invocation-name)
+             nil t nil
+             "-Q" "-batch"
+             "--eval" (prin1-to-string
+                       `(progn
+                          (require 'multisession)
+                          (let ((multisession-directory ,dir)
+                                (multisession-storage 'sqlite)
+                                (user-init-file "/tmp/foo.el"))
+                            (define-multisession-variable foo 0
+                              ""
+                              :synchronized t)
+                            (cl-incf (multisession-value foo))))))
+            (should (= (multisession-value foo) 2)))
+        (sqlite-close multisession--db)
+        (setq multisession--db nil)))))
+
+(ert-deftest multi-test-sqlite-busy ()
+  (skip-unless (and t (sqlite-available-p)))
+  (ert-with-temp-file dir
+    :directory t
+    (let ((user-init-file "/tmp/foo.el")
+          (multisession-directory dir)
+          (multisession-storage 'sqlite)
+          proc)
+      (unwind-protect
+          (progn
+            (define-multisession-variable bar 0
+              ""
+              :synchronized t)
+            (should (= (multisession-value bar) 0))
+            (cl-incf (multisession-value bar))
+            (should (= (multisession-value bar) 1))
+            (setq proc
+                  (start-process
+                   "other-emacs"
+                   nil
+                   (concat invocation-directory invocation-name)
+                   "-Q" "-batch"
+                   "--eval" (prin1-to-string
+                             `(progn
+                                (require 'multisession)
+                                (let ((multisession-directory ,dir)
+                                      (multisession-storage 'sqlite)
+                                      (user-init-file "/tmp/bar.el"))
+                                  (define-multisession-variable bar 0
+                                    "" :synchronized t)
+                                  (dotimes (i 100)
+                                    (cl-incf (multisession-value bar))))))))
+            (while (process-live-p proc)
+              (ignore-error 'sqlite-locked-error
+                (message "bar %s" (multisession-value bar))
+                ;;(cl-incf (multisession-value bar))
+                )
+              (sleep-for 0.1))
+            (message "bar ends up as %s" (multisession-value bar))
+            (should (< (multisession-value bar) 1003)))
+        (sqlite-close multisession--db)
+        (setq multisession--db nil)))))
+
+(ert-deftest multi-test-files-simple ()
+  (ert-with-temp-file dir
+    :directory t
+    (let ((user-init-file "/tmp/sfoo.el")
+          (multisession-storage 'files)
+          (multisession-directory dir))
+      (define-multisession-variable sfoo 0
+        ""
+        :synchronized t)
+      (should (= (multisession-value sfoo) 0))
+      (cl-incf (multisession-value sfoo))
+      (should (= (multisession-value sfoo) 1))
+      (call-process
+       (concat invocation-directory invocation-name)
+       nil t nil
+       "-Q" "-batch"
+       "--eval" (prin1-to-string
+                 `(progn
+                    (require 'multisession)
+                    (let ((multisession-directory ,dir)
+                          (multisession-storage 'files)
+                          (user-init-file "/tmp/sfoo.el"))
+                      (define-multisession-variable sfoo 0
+                        ""
+                        :synchronized t)
+                      (cl-incf (multisession-value sfoo))))))
+      (should (= (multisession-value sfoo) 2)))))
+
+(ert-deftest multi-test-files-busy ()
+  (skip-unless (and t (sqlite-available-p)))
+  (ert-with-temp-file dir
+    :directory t
+    (let ((user-init-file "/tmp/foo.el")
+          (multisession-storage 'files)
+          (multisession-directory dir)
+          proc)
+      (define-multisession-variable sbar 0
+        ""
+        :synchronized t)
+      (should (= (multisession-value sbar) 0))
+      (cl-incf (multisession-value sbar))
+      (should (= (multisession-value sbar) 1))
+      (setq proc
+            (start-process
+             "other-emacs"
+             nil
+             (concat invocation-directory invocation-name)
+             "-Q" "-batch"
+             "--eval" (prin1-to-string
+                       `(progn
+                          (require 'multisession)
+                          (let ((multisession-directory ,dir)
+                                (multisession-storage 'files)
+                                (user-init-file "/tmp/sbar.el"))
+                            (define-multisession-variable sbar 0
+                              "" :synchronized t)
+                            (dotimes (i 1000)
+                              (cl-incf (multisession-value sbar))))))))
+      (while (process-live-p proc)
+        (message "sbar %s" (multisession-value sbar))
+        ;;(cl-incf (multisession-value sbar))
+        (sleep-for 0.1))
+      (message "sbar ends up as %s" (multisession-value sbar))
+      (should (< (multisession-value sbar) 2000)))))
+
+(ert-deftest multi-test-files-some-values ()
+  (ert-with-temp-file dir
+    :directory t
+    (let ((user-init-file "/tmp/sfoo.el")
+          (multisession-storage 'files)
+          (multisession-directory dir))
+      (define-multisession-variable foo1 nil)
+      (should (eq (multisession-value foo1) nil))
+      (setf (multisession-value foo1) nil)
+      (should (eq (multisession-value foo1) nil))
+      (setf (multisession-value foo1) t)
+      (should (eq (multisession-value foo1) t))
+
+      (define-multisession-variable foo2 t)
+      (setf (multisession-value foo2) nil)
+      (should (eq (multisession-value foo2) nil))
+      (setf (multisession-value foo2) t)
+      (should (eq (multisession-value foo2) t))
+
+      (define-multisession-variable foo3 t)
+      (should-error (setf (multisession-value foo3) (make-marker)))
+
+      (let ((string (with-temp-buffer
+                      (set-buffer-multibyte nil)
+                      (insert 0 1 2)
+                      (buffer-string))))
+        (should-not (multibyte-string-p string))
+        (define-multisession-variable foo4 nil)
+        (setf (multisession-value foo4) string)
+        (should (equal (multisession-value foo4) string))))))
+
+;;; multisession-tests.el ends here
diff --git a/test/src/sqlite-tests.el b/test/src/sqlite-tests.el
index d1076e4..27ba74e 100644
--- a/test/src/sqlite-tests.el
+++ b/test/src/sqlite-tests.el
@@ -184,6 +184,7 @@
 
 (ert-deftest sqlite-load-extension ()
   (skip-unless (sqlite-available-p))
+  (skip-unless (fboundp 'sqlite-load-extension))
   (let (db)
     (setq db (sqlite-open))
     (should-error



reply via email to

[Prev in Thread] Current Thread [Next in Thread]