Skip to content

Commit d37d8e7

Browse files
fix: BSD tests (#631)
* fix: update BSD tests workflow and enhance lock tests for signal handling * fix: update BSD tests to use latest action version and improve temp directory creation * fix: update build constraints for executable files to correctly handle BSD and Linux * fix: refactor executable path handling for Linux and BSD systems * fix: refactor TestFileCloseAfterWrite to use synctest for improved concurrency handling * fix: add TestProcessPID to validate child process PID handling on non-NetBSD systems * fix: add TestForceLockWithExpiredPID to validate lock behavior with terminated child processes * fix: update README to enhance feature descriptions and add BSD test badge; refactor lock tests and add executable resolution tests for BSD systems
1 parent d58824b commit d37d8e7

13 files changed

Lines changed: 203 additions & 163 deletions

.github/workflows/BSD-tests.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ name: Run tests on BSDs
22

33
on:
44
workflow_dispatch:
5-
# push:
6-
# branches: [ master ]
7-
# paths-ignore:
8-
# - 'docs/**'
5+
push:
6+
branches: [ master ]
7+
paths-ignore:
8+
- 'docs/**'
99

10-
# pull_request:
11-
# types: [opened, synchronize, reopened]
12-
# paths-ignore:
13-
# - 'docs/**'
10+
pull_request:
11+
types: [opened, synchronize, reopened]
12+
paths-ignore:
13+
- 'docs/**'
1414

1515
jobs:
1616
test-freebsd:
@@ -36,7 +36,7 @@ jobs:
3636
GOOS: freebsd
3737

3838
- name: Run tests in VM
39-
uses: cross-platform-actions/action@v0.32.0
39+
uses: cross-platform-actions/action@v1
4040
with:
4141
operating_system: freebsd
4242
version: '${{ matrix.freebsd_version }}'
@@ -70,7 +70,7 @@ jobs:
7070
GOOS: netbsd
7171

7272
- name: Run tests in VM
73-
uses: cross-platform-actions/action@v0.32.0
73+
uses: cross-platform-actions/action@v1
7474
with:
7575
operating_system: netbsd
7676
version: '${{ matrix.netbsd_version }}'
@@ -104,7 +104,7 @@ jobs:
104104
GOOS: openbsd
105105

106106
- name: Run tests in VM
107-
uses: cross-platform-actions/action@v0.32.0
107+
uses: cross-platform-actions/action@v1
108108
with:
109109
operating_system: openbsd
110110
version: '${{ matrix.openbsd_version }}'

README.md

Lines changed: 43 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,65 @@
11
![Build](https://github.com/creativeprojects/resticprofile/workflows/Build/badge.svg)
2+
![Run tests on FreeBSD, OpenBSD, NetBSD](https://github.com/creativeprojects/resticprofile/actions/workflows/BSD-tests.yml/badge.svg)
23
[![codecov](https://codecov.io/gh/creativeprojects/resticprofile/branch/master/graph/badge.svg?token=cUozgF9j4I)](https://codecov.io/gh/creativeprojects/resticprofile)
34

45
# resticprofile
56
Configuration profiles manager for [restic backup](https://restic.net/)
67

7-
**resticprofile** is the missing link between a configuration file and restic backup. Creating a configuration file for restic has been [discussed before](https://github.com/restic/restic/issues/16), but seems to be a very low priority right now.
8-
9-
With resticprofile:
10-
11-
* You no longer need to remember command parameters and environment variables
12-
* You can create multiple profiles inside one configuration file
13-
* A profile can inherit all the options from another profile
14-
* You can run the forget command before or after a backup (in a section called *retention*)
15-
* You can check a repository before or after a backup
16-
* You can create groups of profiles that will run sequentially
17-
* You can run shell commands before or after running a profile: useful if you need to mount and unmount your backup disk for example
18-
* You can run a shell command if an error occurred (at any time)
19-
* You can send a backup stream via _stdin_
20-
* You can start restic at a lower or higher priority (Priority Class in Windows, *nice* in all unixes) and/or _ionice_ (only available on Linux)
21-
* It can check that you have enough memory before starting a backup. (I've had some backups that literally killed a server with swap disabled)
22-
* You can generate cryptographically secure random keys to use as a restic key file
23-
* You can easily schedule backups, retentions and checks (works for *systemd*, *crond*, *launchd* and *windows task scheduler*)
24-
* You can generate a simple status file to send to some monitoring software and make sure your backups are running fine
25-
* You can use a template syntax in your configuration file
26-
* You can generate scheduled tasks using *crond*
27-
* Get backup statistics in your status file
28-
* Automatically clear up stale locks
29-
* Export a **prometheus** file after a backup, or send the report to a push gateway automatically
30-
* Run shell commands in the background when non fatal errors are detected from restic
31-
* Send messages to HTTP hooks before, after a successful or failed job (backup, forget, check, prune, copy)
32-
* Automatically initialize the secondary repository using `copy-chunker-params` flag
33-
* Send resticprofile logs to a syslog server
34-
* Preventing your system from idle sleeping
35-
* See the help from both restic and resticprofile via the `help` command or `-h` flag
36-
* Don't schedule a job when the system is running on battery
37-
* **[new for v0.29.0]** Scheduling a group of profiles (configuration `v2` only)
8+
**resticprofile** is the missing link between a configuration file and restic backup command line.
9+
10+
## Features
11+
12+
Here is a non-exhaustive list of what resticprofile offers:
13+
14+
* **Profiles**
15+
* No need to remember command parameters and environment variables
16+
* Create multiple profiles in one configuration file
17+
* Profiles can inherit options from other profiles
18+
* Create groups of profiles to run sequentially
19+
* Easily schedule backups, retentions, and checks (supports *systemd*, *crond*, *launchd*, and *Windows Task Scheduler*)
20+
* Use template syntax in your configuration file
21+
* **[new for v0.29.0]** Schedule a group of profiles (configuration `v2` only)
22+
* **Automation**
23+
* Run the forget command before or after a backup (in a section called *retention*)
24+
* Check a repository before or after a backup
25+
* Run shell commands before or after running a profile, useful for mounting and unmounting backup disks
26+
* Run a shell command if an error occurs
27+
* Send a backup stream via _stdin_
28+
* Start restic at different priorities (Priority Class in Windows, *nice* in Unix, and/or _ionice_ in Linux)
29+
* Automatically clear stale locks
30+
* **Monitoring**
31+
* Generate a simple status file for monitoring software to ensure backups are running smoothly
32+
* Export a prometheus file after a backup or send the report to a push gateway
33+
* Run shell commands in the background when non-fatal errors are detected
34+
* Send messages to HTTP hooks before, after a successful or failed job (backup, forget, check, prune, copy)
35+
* Send resticprofile logs to a syslog server
36+
* **Checks**
37+
* Check for enough memory before starting a backup
38+
* Avoid scheduling a job when the system is on battery
39+
* **Misc**
40+
* Generate cryptographically secure random keys for a restic key file
41+
* Automatically initialize the secondary repository using the `copy-chunker-params` flag
42+
* Prevent the system from idle sleeping
43+
* View help for both restic and resticprofile via the `help` command or `-h` flag
44+
45+
## Configuration files
3846

3947
The configuration file accepts various formats:
40-
* [TOML](https://github.com/toml-lang/toml) : configuration file with extension _.toml_ and _.conf_ to keep compatibility with versions before 0.6.0
48+
* [TOML](https://github.com/toml-lang/toml) : configuration file with extension _.toml_ or _.conf_
4149
* [JSON](https://en.wikipedia.org/wiki/JSON) : configuration file with extension _.json_
4250
* [YAML](https://en.wikipedia.org/wiki/YAML) : configuration file with extension _.yaml_
4351
* [HCL](https://github.com/hashicorp/hcl): configuration file with extension _.hcl_
4452

4553

46-
47-
<!--ts-->
48-
49-
<!--te-->
54+
## Getting started
5055

5156
We recommend you start by reading the [getting started](https://creativeprojects.github.io/resticprofile/configuration/getting_started/index.html) section
5257

53-
# Using resticprofile
58+
## Using resticprofile
5459

5560
The full documentation has been moved to [creativeprojects.github.io](https://creativeprojects.github.io/resticprofile/)
5661

57-
# Survey
62+
## Survey
5863

5964
What are your most important features?
6065
Please fill in the [survey](https://github.com/creativeprojects/resticprofile/issues/415)

lock/lock_pid_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//go:build !netbsd
2+
3+
package lock
4+
5+
import (
6+
"bytes"
7+
"os"
8+
"os/signal"
9+
"path/filepath"
10+
"testing"
11+
12+
"github.com/creativeprojects/resticprofile/shell"
13+
"github.com/shirou/gopsutil/v4/process"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
func TestProcessPID(t *testing.T) {
19+
t.Parallel()
20+
21+
var childPID int32
22+
buffer := &bytes.Buffer{}
23+
24+
// use the lock helper binary (we only need to wait for some time, we don't need the locking part)
25+
cmd := shell.NewCommand(lockBinary, []string{"lock", "-wait", "200", "-lock", filepath.Join(t.TempDir(), t.Name())})
26+
cmd.Stdout = buffer
27+
// SetPID method is called right after we forked and have a PID available
28+
cmd.SetPID = func(pid int32) {
29+
childPID = pid
30+
running, err := process.PidExists(childPID)
31+
assert.NoError(t, err)
32+
assert.True(t, running)
33+
}
34+
_, _, err := cmd.Run()
35+
require.NoError(t, err)
36+
37+
// at that point, the child process should be finished
38+
running, err := process.PidExists(childPID)
39+
assert.NoError(t, err)
40+
assert.False(t, running)
41+
}
42+
43+
func TestForceLockWithExpiredPID(t *testing.T) {
44+
t.Parallel()
45+
46+
tempfile := getTempfile(t)
47+
lock := NewLock(tempfile)
48+
defer lock.Release()
49+
50+
assert.True(t, lock.TryAcquire())
51+
assert.True(t, lock.HasLocked())
52+
53+
// run a child process
54+
c := make(chan os.Signal, 1)
55+
signal.Notify(c, os.Interrupt)
56+
defer signal.Reset(os.Interrupt)
57+
58+
cmd := shell.NewSignalledCommand("echo", []string{"Hello World!"}, c)
59+
cmd.SetPID = lock.SetPID
60+
_, _, err := cmd.Run()
61+
require.NoError(t, err)
62+
63+
// child process should be finished
64+
// let's close the lockfile handle manually (unix doesn't actually care, but windows would complain)
65+
lock.file.Close()
66+
67+
other := NewLock(tempfile)
68+
defer other.Release()
69+
assert.True(t, other.ForceAcquire())
70+
assert.True(t, other.HasLocked())
71+
}

lock/lock_test.go

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@ import (
66
"fmt"
77
"os"
88
"os/exec"
9-
"os/signal"
109
"path/filepath"
1110
"regexp"
1211
"testing"
1312
"time"
1413

1514
"github.com/creativeprojects/resticprofile/platform"
1615
"github.com/creativeprojects/resticprofile/shell"
17-
"github.com/shirou/gopsutil/v4/process"
1816
"github.com/stretchr/testify/assert"
1917
"github.com/stretchr/testify/require"
2018
)
@@ -136,31 +134,6 @@ func TestSetMorePID(t *testing.T) {
136134
assert.Equal(t, int32(13), pid)
137135
}
138136

139-
func TestProcessPID(t *testing.T) {
140-
t.Parallel()
141-
142-
var childPID int32
143-
buffer := &bytes.Buffer{}
144-
145-
// use the lock helper binary (we only need to wait for some time, we don't need the locking part)
146-
cmd := shell.NewCommand(lockBinary, []string{"lock", "-wait", "200", "-lock", filepath.Join(t.TempDir(), t.Name())})
147-
cmd.Stdout = buffer
148-
// SetPID method is called right after we forked and have a PID available
149-
cmd.SetPID = func(pid int32) {
150-
childPID = pid
151-
running, err := process.PidExists(childPID)
152-
assert.NoError(t, err)
153-
assert.True(t, running)
154-
}
155-
_, _, err := cmd.Run()
156-
require.NoError(t, err)
157-
158-
// at that point, the child process should be finished
159-
running, err := process.PidExists(childPID)
160-
assert.NoError(t, err)
161-
assert.False(t, running)
162-
}
163-
164137
func TestForceLockIsAvailable(t *testing.T) {
165138
t.Parallel()
166139

@@ -187,36 +160,6 @@ func TestForceLockWithNoPID(t *testing.T) {
187160
assert.False(t, other.HasLocked())
188161
}
189162

190-
func TestForceLockWithExpiredPID(t *testing.T) {
191-
t.Parallel()
192-
193-
tempfile := getTempfile(t)
194-
lock := NewLock(tempfile)
195-
defer lock.Release()
196-
197-
assert.True(t, lock.TryAcquire())
198-
assert.True(t, lock.HasLocked())
199-
200-
// run a child process
201-
c := make(chan os.Signal, 1)
202-
signal.Notify(c, os.Interrupt)
203-
defer signal.Reset(os.Interrupt)
204-
205-
cmd := shell.NewSignalledCommand("echo", []string{"Hello World!"}, c)
206-
cmd.SetPID = lock.SetPID
207-
_, _, err := cmd.Run()
208-
require.NoError(t, err)
209-
210-
// child process should be finished
211-
// let's close the lockfile handle manually (unix doesn't actually care, but windows would complain)
212-
lock.file.Close()
213-
214-
other := NewLock(tempfile)
215-
defer other.Release()
216-
assert.True(t, other.ForceAcquire())
217-
assert.True(t, other.HasLocked())
218-
}
219-
220163
func TestForceLockWithRunningPID(t *testing.T) {
221164
t.Parallel()
222165

shell/command_test.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,23 @@ func TestVariablesRewrite(t *testing.T) {
271271
func TestRunLocalCommand(t *testing.T) {
272272
t.Parallel()
273273

274-
cmd := NewCommand("command.go", nil)
275-
expected := "." + string(os.PathSeparator) + "command.go"
274+
// find a file in the current directory
275+
entries, err := os.ReadDir("./")
276+
require.NoError(t, err)
277+
278+
localFilename := ""
279+
for _, entry := range entries {
280+
if !entry.IsDir() && entry.Name()[0] != '.' {
281+
localFilename = entry.Name()
282+
break
283+
}
284+
}
285+
require.NotEmpty(t, localFilename, "no file found in current directory")
286+
287+
t.Logf("using local file %q", localFilename)
288+
289+
cmd := NewCommand(localFilename, nil)
290+
expected := "." + string(os.PathSeparator) + localFilename
276291

277292
for _, shell := range []string{defaultShell, bashShell, powershell, powershell6, windowsShell} {
278293
args := getArgumentsComposer(shell)(cmd)

util/executable.go

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux
1+
//go:build linux || freebsd || openbsd || netbsd
22

33
package util
44

@@ -36,7 +36,7 @@ func TestResolveExecutable(t *testing.T) {
3636
})
3737

3838
t.Run("command in PATH", func(t *testing.T) {
39-
// Testing with "ls" which should be available on most Linux systems
39+
// Testing with "ls" which should be available on all unix systems
4040
path, err := resolveExecutable("ls")
4141
assert.NoError(t, err)
4242
assert.NotEmpty(t, path)

util/executable_os.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
//go:build darwin || windows
2+
3+
package util
4+
5+
import "os"
6+
7+
// Executable returns the path name for the executable that started the current process.
8+
// On Darwin and Windows, it behaves like os.Executable.
9+
// On Linux and BSDs, it returns the path to the executable as specified in the command line arguments.
10+
func Executable() (string, error) {
11+
return os.Executable()
12+
}
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//go:build linux
1+
//go:build !darwin && !windows
22

33
package util
44

@@ -10,8 +10,8 @@ import (
1010
)
1111

1212
// Executable returns the path name for the executable that started the current process.
13-
// On non-Linux systems, it behaves like os.Executable.
14-
// On Linux, it returns the path to the executable as specified in the command line arguments.
13+
// On Darwin and Windows, it behaves like os.Executable.
14+
// On Linux and BSDs, it returns the path to the executable as specified in the command line arguments.
1515
func Executable() (string, error) {
1616
return resolveExecutable(os.Args[0])
1717
}

0 commit comments

Comments
 (0)