diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01502b1..7c180bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,36 +1,77 @@ -# This is a basic workflow to help you get started with Actions +--- +# +# Copyright (C) 2024 James Cherti +# URL: https://github.com/jamescherti/easysession.el +# +# This file 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 2, or (at your option) +# any later version. +# +# This file 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 . +# name: CI +on: [push, pull_request] -# Controls when the workflow will run -on: - # Triggers the workflow on push or pull request events but only for the "main" branch - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "build" - build: - # The type of runner that the job will run on + test: runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job + strategy: + matrix: + emacs-version: + - 26.3 + - 27.1 + - 28.2 + - 29.1 + - 29.4 + python-version: + - 3.11 steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v4 + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - uses: purcell/setup-emacs@master + with: + version: ${{ matrix.emacs-version }} + - uses: actions/cache@v2 + id: cache-cask-packages + with: + path: .cask + key: cache-cask-packages-000 + - uses: actions/cache@v2 + id: cache-cask-executable + with: + path: ~/.cask + key: cache-cask-executable-000 + - uses: cask/setup-cask@master + if: steps.cache-cask-executable.outputs.cache-hit != 'true' + with: + version: snapshot + - run: echo "$HOME/.cask/bin" >> $GITHUB_PATH - # Runs a single command using the runners shell - - name: Run a one-line script - run: echo Hello, world! - - # Runs a set of commands using the runners shell - - name: Run a multi-line script + - name: Install run: | - echo Add other actions to build, - echo test, and deploy your project. + python -m pip install --upgrade pip + sudo apt-get install emacs && emacs --version + git clone https://github.com/riscy/melpazoid.git ~/melpazoid + pip install ~/melpazoid + + - name: Run melpazoid + env: + RECIPE: (easysession :repo "jamescherti/easysession.el" :branch "${{ github.ref_name }}" :fetcher github :files ("*.el")) + EXIST_OK: false + run: echo $GITHUB_REF && make -C ~/melpazoid + + - name: Run tests + run: make test + env: + CASK_PATH: $HOME/.cask/bin diff --git a/Cask b/Cask new file mode 100644 index 0000000..1969ca7 --- /dev/null +++ b/Cask @@ -0,0 +1,4 @@ +(source gnu) +(source melpa) + +(package-file "easysession.el") diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0ddeb51 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +# +# Copyright (C) 2024 James Cherti +# URL: https://github.com/jamescherti/easysession.el +# +# This file 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 2, or (at your option) +# any later version. +# +# This file 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 . +# + +export EMACS ?= $(shell command -v emacs 2>/dev/null) +CASK_DIR := $(shell cask package-directory) + +$(CASK_DIR): Cask + cask install + @touch $(CASK_DIR) + +.PHONY: cask +cask: $(CASK_DIR) + +.PHONY: compile +compile: cask + cask emacs -batch -L . -L test \ + --eval "(setq byte-compile-error-on-warn t)" \ + -f batch-byte-compile $$(cask files); \ + (ret=$$? ; cask clean-elc && exit $$ret) + +.PHONY: test +test: compile + cask emacs --batch -L . -L tests -l tests/test-easysession.el -f test-easysession diff --git a/README.md b/README.md index c658286..7929d13 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # easysession.el - Easily persist and restore your Emacs editing sessions [![MELPA](https://melpa.org/packages/easysession-badge.svg)](https://melpa.org/#/easysession) ![](https://raw.githubusercontent.com/jamescherti/easysession.el/main/.images/made-for-gnu-emacs.svg) +![Build Status](https://github.com/jamescherti/easysession.el/actions/workflows/ci.yml/badge.svg) ![License](https://img.shields.io/github/license/jamescherti/easysession.el) The `easysession.el` Emacs package is a lightweight session manager for Emacs that can persist and restore file editing buffers, indirect buffers/clones, Dired buffers, windows/splits, the built-in tab-bar (including tabs, their buffers, and windows), and Emacs frames. It offers a convenient and effortless way to manage Emacs editing sessions and utilizes built-in Emacs functions to persist and restore frames. diff --git a/easysession.el b/easysession.el index b90e47c..78f340d 100644 --- a/easysession.el +++ b/easysession.el @@ -350,7 +350,7 @@ Raise an error if the session name is invalid." (error "[easysession] Invalid session name: %s" session-name)) session-name) -(defun easysession-set-current-session (&optional session-name) +(defun easysession--set-current-session (&optional session-name) "Set the current session to SESSION-NAME. Return t if the session name is successfully set." (easysession--ensure-session-name-valid session-name) @@ -716,8 +716,9 @@ SESSION-NAME is the name of the session." 'utf-8 session-file) t))) (if fwrite-success - (when (called-interactively-p 'any) - (easysession--message "Session saved: %s" session-name) + (progn + (when (called-interactively-p 'any) + (easysession--message "Session saved: %s" session-name)) (run-hooks 'easysession-after-save-hook)) (error "[easysession] %s: failed to save the session to %s" session-name session-file))) @@ -798,7 +799,7 @@ If the function is called interactively, ask the user." (previous-session-name easysession--current-session-name)) (easysession--ensure-session-name-valid new-session-name) (easysession-save new-session-name) - (easysession-set-current-session new-session-name) + (easysession--set-current-session new-session-name) (if (string= previous-session-name easysession--current-session-name) (easysession--message "Saved the session: %s" new-session-name) (easysession--message "Saved and switched to session: %s" @@ -841,7 +842,7 @@ initialized." (easysession-load session-name) (unless session-reloaded - (easysession-set-current-session session-name) + (easysession--set-current-session session-name) (unless (file-exists-p session-file) (run-hooks 'easysession-new-session-hook) (easysession-save) diff --git a/tests/test-easysession.el b/tests/test-easysession.el new file mode 100644 index 0000000..a0f31e5 --- /dev/null +++ b/tests/test-easysession.el @@ -0,0 +1,276 @@ +;;; test-easysession.el --- Easysession tests -*- lexical-binding: t -*- + +;; Copyright (C) 2024 James Cherti | https://www.jamescherti.com/contact/ + +;; Author: James Cherti +;; URL: https://github.com/jamescherti/easysession.el +;; Keywords: convenience +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; This file 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 2, or (at your option) +;; any later version. + +;; This file 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 . + +;;; Commentary: +;; Test the easysession package. + +;;; Code: + +(require 'dired) +(require 'easysession) + +(defvar test-easysession--before-load-hook-triggered nil + "Flag indicating whether `easysession-before-load-hook' has been executed.") + +(defvar test-easysession--after-load-hook-triggered nil + "Flag indicating whether `easysession-after-load-hook' has been executed.") + +(defvar test-easysession--before-save-hook-triggered nil + "Flag indicating whether `easysession-before-save-hook' has been executed.") + +(defvar test-easysession--after-save-hook-triggered nil + "Flag indicating whether `easysession-after-save-hook' has been executed.") + +(defvar test-easysession--new-session-hook-triggered nil + "Flag indicating whether `easysession-new-session-hook' has been executed.") + +(defvar test-easysession--file-buffer1-path "~/buffer1" + "Path to the first test file editing buffer.") + +(defvar test-easysession--file-buffer2-path "~/buffer2" + "Path to the second test file editing buffer.") + +(defvar test-easysession--dired-buffer-path "~/" + "Path to the directory for the Dired buffer.") + +(defvar test-easysession--dired-buffer nil + "Reference to the Dired buffer.") + +(defvar test-easysession--file-buffer1 nil + "Reference to the first test buffer.") + +(defvar test-easysession--file-buffer2 nil + "Reference to the second test buffer.") + +(defvar test-easysession--indirect-buffer1-name "indirect-buffer1" + "Name of the indirect test buffer.") + +(defvar test-easysession--indirect-buffer1 nil + "Reference to the indirect test buffer.") + +(defun test-easysession--add-hooks () + "Add and configure hooks for testing `easysession`. +Tracks the execution of session-related hooks and performs checks +to ensure expected buffer states before and after loading or saving." + (interactive) + (when test-easysession--new-session-hook-triggered + (error (concat "The `easysession-new-session-hook` should not be " + "triggered before the switch to another session."))) + + (add-hook 'easysession-new-session-hook + #'(lambda () (setq test-easysession--new-session-hook-triggered t))) + + (add-hook 'easysession-before-load-hook + #'(lambda () + (when (get-file-buffer test-easysession--file-buffer1-path) + (error "Before-load: Buffer 1 should not be open")) + (setq test-easysession--before-load-hook-triggered t))) + + (add-hook 'easysession-after-load-hook + #'(lambda () + (setq test-easysession--after-load-hook-triggered t))) + + (add-hook 'easysession-before-save-hook + #'(lambda () + (setq test-easysession--before-save-hook-triggered t))) + + (add-hook 'easysession-after-save-hook + #'(lambda () + (setq test-easysession--after-save-hook-triggered t)))) + +(defun test-easysession--switch-session () + "Test the `easysession-switch-to' function. +Test the `easysession-switch-to' function by switching to a test session. Checks +if the `easysession-new-session-hook' is correctly executed and verifies the +session name before and after the switch." + (interactive) + ;; Verify the initial session name + (unless (string= "main" (easysession-get-session-name)) + (error ("Expected the initial session to be named 'main', but found '%s'" + (easysession-get-session-name)))) + + ;; Switch to the test session + (easysession-save) + (easysession-switch-to "test") + + ;; Verify the session name after switching + (unless (string= "test" (easysession-get-session-name)) + (error "Expected the session to be named 'test', but found '%s'" + (easysession-get-session-name))) + + ;; Check if the new session hook was executed + (unless test-easysession--new-session-hook-triggered + (error (concat "The `easysession-new-session-hook` was not triggered " + "after switching sessions.")))) + +(defun test-easysession--add-remove-handlers () + "Test adding and removing easysession save and load handlers. +This function ensures that handlers are correctly removed and re-added, and +validates the handler lists after each operation." + (interactive) + ;; Remove existing save and load handlers + (easysession-remove-save-handler 'easysession--handler-save-file-editing-buffers) + (easysession-remove-save-handler 'easysession--handler-save-indirect-buffers) + (easysession-remove-load-handler 'easysession--handler-load-file-editing-buffers) + (easysession-remove-load-handler 'easysession--handler-load-indirect-buffers) + + ;; Validate that handler lists are empty after removal + (unless (null easysession--load-handlers) + (error "Load handlers list is not empty after removal")) + (unless (null easysession--save-handlers) + (error "Save handlers list is not empty after removal")) + + ;; Re-add save and load handlers + (easysession-add-save-handler 'easysession--handler-save-file-editing-buffers) + (easysession-add-save-handler 'easysession--handler-save-indirect-buffers) + (easysession-add-load-handler 'easysession--handler-load-file-editing-buffers) + (easysession-add-load-handler 'easysession--handler-load-indirect-buffers) + + ;; Validate that handlers were correctly added + (unless (equal easysession--load-handlers + '(easysession--handler-load-file-editing-buffers + easysession--handler-load-indirect-buffers)) + (error "Load handlers were not added correctly")) + (unless (equal easysession--save-handlers + '(easysession--handler-save-file-editing-buffers + easysession--handler-save-indirect-buffers)) + (error "Save handlers were not added correctly"))) + +(defun test-easysession--create-buffers () + "Create and set up test buffers for easysession. +This function creates file buffers, a Dired buffer, and an indirect buffer, +storing them in respective variables for later use." + (interactive) + ;; File editing buffers + (with-temp-buffer + (insert "hello world") + (write-file test-easysession--file-buffer1-path)) + + (with-temp-buffer + (insert "hello world2") + (write-file test-easysession--file-buffer2-path)) + + (setq test-easysession--file-buffer1 + (find-file-noselect test-easysession--file-buffer1-path)) + (unless test-easysession--file-buffer1 + (error "Failed to create test-easysession--file-buffer1")) + + (setq test-easysession--file-buffer2 + (find-file-noselect test-easysession--file-buffer2-path)) + (unless test-easysession--file-buffer2 + (error "Failed to create test-easysession--file-buffer2")) + + ;; Dired buffer + (setq test-easysession--dired-buffer + (dired-noselect test-easysession--dired-buffer-path)) + (unless test-easysession--dired-buffer + (error "Failed to create test-easysession--dired-buffer")) + + ;; Indirect buffer + (with-current-buffer test-easysession--file-buffer1 + (clone-indirect-buffer test-easysession--indirect-buffer1-name nil)) + (setq test-easysession--indirect-buffer1 + (get-buffer test-easysession--indirect-buffer1-name)) + (unless test-easysession--indirect-buffer1 + (error "Failed to create test-easysession--indirect-buffer1"))) + +(defun test-easysession--save-load () + "Test persisting and restoring: file editing buffers and indirect-buffer." + (interactive) + (unless (get-file-buffer test-easysession--file-buffer1-path) + (error "Before-save: Buffer 1 should be open")) + + ;; Save session and kill buffers + (easysession-save) + (unless test-easysession--after-save-hook-triggered + (error "The easysession-after-save-hook was not triggered")) + (unless test-easysession--before-save-hook-triggered + (error "The easysession-before-save-hook was not triggered")) + (kill-buffer test-easysession--file-buffer1) + (when (get-file-buffer test-easysession--file-buffer1-path) + (error "The second buffer is still open")) + + (kill-buffer test-easysession--file-buffer2) + (when (get-file-buffer test-easysession--file-buffer2-path) + (error "The second buffer is still open")) + + (kill-buffer test-easysession--dired-buffer) + (when (buffer-live-p test-easysession--dired-buffer) + (error "The Dired buffer is still open")) + + (kill-buffer test-easysession--indirect-buffer1) + (when (get-buffer test-easysession--indirect-buffer1-name) + (error "The indirect buffer is still open")) + + ;; Load session + (easysession-load) + (unless test-easysession--after-load-hook-triggered + (error "The easysession-after-load-hook was not triggered")) + (unless test-easysession--before-load-hook-triggered + (error "The easysession-before-load-hook was not triggered")) + + (setq test-easysession--file-buffer1 + (get-file-buffer test-easysession--file-buffer1-path)) + (when (not test-easysession--file-buffer1) + (error "Failed to first buffer")) + + (setq test-easysession--file-buffer2 + (get-file-buffer test-easysession--file-buffer2-path)) + (when (not test-easysession--file-buffer2) + (error "Failed to the second buffer")) + + (setq test-easysession--indirect-buffer1 + (get-buffer test-easysession--indirect-buffer1-name)) + (when (not test-easysession--indirect-buffer1) + (error + "Failed to restore the indirect buffer")) + + (setq test-easysession--dired-buffer + (dired-noselect test-easysession--dired-buffer-path)) + (when (not test-easysession--dired-buffer) + (error + "Failed to restore the Dired buffer")) + (with-current-buffer test-easysession--dired-buffer + (unless (string= (expand-file-name test-easysession--dired-buffer-path) + (expand-file-name default-directory)) + (error + "The Dired buffer points to the wrong path")))) + +(defun test-easysession--get-all-names () + "Test: `easysession--get-all-names'." + (interactive) + (unless (equal (easysession--get-all-names) '("main" "test")) + (error "The easysession--get-all-names failed"))) + +(defun test-easysession () + "Test easysession." + (interactive) + (test-easysession--add-hooks) + (test-easysession--switch-session) + (test-easysession--add-remove-handlers) + (test-easysession--create-buffers) + (test-easysession--save-load) + (test-easysession--get-all-names) + (message "Success: test-easysession")) + +(provide 'test-easysession) +;;; test-easysession.el ends here