From 13809f4584bbd783e113b8c20e550c0901c452c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?=
 <bjorn.erik.pedersen@gmail.com>
Date: Fri, 5 May 2023 15:06:33 +0200
Subject: [PATCH] Use unix.CloneFile on MacOs

To fix unexpected errors of type:

```
[signal: killed]
FAIL: testscripts/myecho.txt:1: unexpected command failure
```

Fixes #200
---
 go.mod                         |  1 +
 go.sum                         |  2 ++
 testscript/clonefile.go        | 12 ++++++++++++
 testscript/clonefile_darwin.go |  8 ++++++++
 testscript/clonefile_other.go  | 11 +++++++++++
 testscript/exe.go              |  6 ++----
 6 files changed, 36 insertions(+), 4 deletions(-)
 create mode 100644 testscript/clonefile.go
 create mode 100644 testscript/clonefile_darwin.go
 create mode 100644 testscript/clonefile_other.go

diff --git a/go.mod b/go.mod
index 5c8d3056..05eeb512 100644
--- a/go.mod
+++ b/go.mod
@@ -4,5 +4,6 @@ go 1.19
 
 require (
 	golang.org/x/mod v0.9.0
+	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
 	golang.org/x/tools v0.1.12
 )
diff --git a/go.sum b/go.sum
index 0c7672cb..d8a340c0 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,6 @@
 golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
 golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/testscript/clonefile.go b/testscript/clonefile.go
new file mode 100644
index 00000000..0ae053f2
--- /dev/null
+++ b/testscript/clonefile.go
@@ -0,0 +1,12 @@
+//go:build unix && !darwin
+// +build unix,!darwin
+
+package testscript
+
+import "golang.org/x/sys/unix"
+
+// cloneFile creates to as a hard link to the from file.
+// If there is an error, it will be of type *LinkError.
+func cloneFile(from, to string) error {
+	return os.Link(from, to)
+}
diff --git a/testscript/clonefile_darwin.go b/testscript/clonefile_darwin.go
new file mode 100644
index 00000000..c0e7a9e6
--- /dev/null
+++ b/testscript/clonefile_darwin.go
@@ -0,0 +1,8 @@
+package testscript
+
+import "golang.org/x/sys/unix"
+
+// cloneFile clones the file from to the file to.
+func cloneFile(from, to string) error {
+	return unix.Clonefile(from, to, 0)
+}
diff --git a/testscript/clonefile_other.go b/testscript/clonefile_other.go
new file mode 100644
index 00000000..a53b344a
--- /dev/null
+++ b/testscript/clonefile_other.go
@@ -0,0 +1,11 @@
+//go:build !unix
+// +build !unix
+
+package testscript
+
+import "os"
+
+// We don't want to use hard links on Windows, as that can lead to "access denied" errors when removing.
+func cloneFile(from, to string) error {
+	return fmt.Errorf("unavailable")
+}
diff --git a/testscript/exe.go b/testscript/exe.go
index ed6bd98d..475ab70f 100644
--- a/testscript/exe.go
+++ b/testscript/exe.go
@@ -122,10 +122,8 @@ func RunMain(m TestingM, commands map[string]func() int) (exitCode int) {
 // system's temporary directory, like we do. We don't use hard links on Windows,
 // as that can lead to "access denied" errors when removing.
 func copyBinary(from, to string) error {
-	if runtime.GOOS != "windows" {
-		if err := os.Link(from, to); err == nil {
-			return nil
-		}
+	if err := cloneFile(from, to); err == nil {
+		return nil
 	}
 	writer, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE, 0o777)
 	if err != nil {