mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Merge 5f18c08711
into eba25e2fed
This commit is contained in:
commit
44fa9d5454
@ -8,7 +8,9 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"os/user"
|
"os/user"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -76,12 +78,19 @@ func TestRead_PathNoPermission(t *testing.T) {
|
|||||||
}
|
}
|
||||||
f.Close()
|
f.Close()
|
||||||
|
|
||||||
if err := os.Chmod(f.Name(), 0); err != nil {
|
if runtime.GOOS == "windows" {
|
||||||
t.Fatalf("err: %s", err)
|
// Use cacls to remove all permissions for this file on Windows
|
||||||
|
cmd := exec.Command("cmd", "/c", "cacls", f.Name(), "/E", "/R", os.Getenv("USERNAME")) // #nosec G204
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
t.Fatalf("Failed to set file permissions with cacls: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := os.Chmod(f.Name(), 0); err != nil {
|
||||||
|
t.Fatalf("Failed to chmod file: %s", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := ReadPathOrContents(f.Name())
|
contents, err := ReadPathOrContents(f.Name())
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("Expected error, got none!")
|
t.Fatal("Expected error, got none!")
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ package local
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
@ -40,6 +41,13 @@ func TestLocal(t *testing.T) *Local {
|
|||||||
local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
|
local.StateWorkspaceDir = filepath.Join(tempDir, "state.tfstate.d")
|
||||||
local.ContextOpts = &tofu.ContextOpts{}
|
local.ContextOpts = &tofu.ContextOpts{}
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
})
|
||||||
return local
|
return local
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
@ -147,7 +148,12 @@ func (m noopMatcher) String() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const osWindows = "windows"
|
||||||
|
|
||||||
func TestBackendConfig_Authentication(t *testing.T) {
|
func TestBackendConfig_Authentication(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
EnableEc2MetadataServer bool
|
EnableEc2MetadataServer bool
|
||||||
@ -634,6 +640,19 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
tc.config["region"] = "us-east-1"
|
tc.config["region"] = "us-east-1"
|
||||||
tc.config["bucket"] = "bucket"
|
tc.config["bucket"] = "bucket"
|
||||||
@ -754,6 +773,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestBackendConfig_Authentication_AssumeRoleInline(t *testing.T) {
|
func TestBackendConfig_Authentication_AssumeRoleInline(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
EnableEc2MetadataServer bool
|
EnableEc2MetadataServer bool
|
||||||
@ -1100,6 +1122,19 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
@ -1194,6 +1229,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) {
|
func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
EnableEc2MetadataServer bool
|
EnableEc2MetadataServer bool
|
||||||
@ -1502,6 +1540,19 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
@ -1598,6 +1649,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
|
func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
SetConfig bool
|
SetConfig bool
|
||||||
@ -1775,6 +1829,19 @@ web_identity_token_file = no-such-file
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
@ -1812,13 +1879,20 @@ web_identity_token_file = no-such-file
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tc.ExpandEnvVars {
|
if tc.ExpandEnvVars {
|
||||||
|
var prefix string
|
||||||
tmpdir := os.Getenv("TMPDIR")
|
tmpdir := os.Getenv("TMPDIR")
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
tmpdir = os.Getenv("TEMP")
|
||||||
|
prefix = tmpdir
|
||||||
|
} else {
|
||||||
|
prefix = "$TMPDIR"
|
||||||
|
}
|
||||||
rel, err := filepath.Rel(tmpdir, tokenFileName)
|
rel, err := filepath.Rel(tmpdir, tokenFileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("error making path relative: %s", err)
|
t.Fatalf("error making path relative: %s", err)
|
||||||
}
|
}
|
||||||
t.Logf("relative: %s", rel)
|
t.Logf("relative: %s", rel)
|
||||||
tokenFileName = filepath.Join("$TMPDIR", rel)
|
tokenFileName = filepath.Join(prefix, rel)
|
||||||
t.Logf("env tempfile: %s", tokenFileName)
|
t.Logf("env tempfile: %s", tokenFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1876,6 +1950,9 @@ web_identity_token_file = no-such-file
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConfig_Region(t *testing.T) {
|
func TestBackendConfig_Region(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
EnvironmentVariables map[string]string
|
EnvironmentVariables map[string]string
|
||||||
@ -2034,6 +2111,19 @@ region = us-west-2
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
tc.config["bucket"] = "bucket"
|
tc.config["bucket"] = "bucket"
|
||||||
tc.config["key"] = "key"
|
tc.config["key"] = "key"
|
||||||
@ -2092,6 +2182,9 @@ region = us-west-2
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBackendConfig_RetryMode(t *testing.T) {
|
func TestBackendConfig_RetryMode(t *testing.T) {
|
||||||
|
testDirectory := t.TempDir()
|
||||||
|
sysRoot := os.Getenv("SYSTEMROOT")
|
||||||
|
|
||||||
testCases := map[string]struct {
|
testCases := map[string]struct {
|
||||||
config map[string]any
|
config map[string]any
|
||||||
EnvironmentVariables map[string]string
|
EnvironmentVariables map[string]string
|
||||||
@ -2143,6 +2236,19 @@ func TestBackendConfig_RetryMode(t *testing.T) {
|
|||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
servicemocks.InitSessionTestEnv(t)
|
servicemocks.InitSessionTestEnv(t)
|
||||||
|
|
||||||
|
// Set Windows-specific environment variables
|
||||||
|
if runtime.GOOS == osWindows {
|
||||||
|
t.Setenv("TEMP", testDirectory)
|
||||||
|
t.Setenv("TMP", testDirectory)
|
||||||
|
t.Setenv("SYSTEMROOT", sysRoot)
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir RemoveAll cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
runtime.GC()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Populate required fields
|
// Populate required fields
|
||||||
tc.config["bucket"] = "bucket"
|
tc.config["bucket"] = "bucket"
|
||||||
tc.config["key"] = "key"
|
tc.config["key"] = "key"
|
||||||
|
@ -8,6 +8,7 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -304,6 +305,7 @@ func TestBackendStates(t *testing.T, b Backend) {
|
|||||||
if v := foo.State(); v.HasManagedResourceInstanceObjects() {
|
if v := foo.State(); v.HasManagedResourceInstanceObjects() {
|
||||||
t.Fatalf("should be empty: %s", v)
|
t.Fatalf("should be empty: %s", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// and delete it again
|
// and delete it again
|
||||||
if err := b.DeleteWorkspace("foo", true); err != nil {
|
if err := b.DeleteWorkspace("foo", true); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
@ -325,6 +327,14 @@ func TestBackendStates(t *testing.T, b Backend) {
|
|||||||
t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected)
|
t.Fatalf("wrong workspaces list\ngot: %#v\nwant: %#v", workspaces, expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger garbage collection to ensure that all open file handles are closed.
|
||||||
|
// This prevents TempDir cleanup errors on Windows.
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestBackendStateLocks will test the locking functionality of the remote
|
// TestBackendStateLocks will test the locking functionality of the remote
|
||||||
|
@ -172,7 +172,16 @@ func (s *Filesystem) persistState(schemas *tofu.Schemas) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer s.stateFileOut.Sync()
|
|
||||||
|
// Sync and close the file handle after all operations are complete
|
||||||
|
defer func() {
|
||||||
|
if err := s.stateFileOut.Sync(); err != nil {
|
||||||
|
log.Printf("Error syncing statefile: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.stateFileOut.Close()
|
||||||
|
s.stateFileOut = nil
|
||||||
|
}()
|
||||||
|
|
||||||
if s.file == nil {
|
if s.file == nil {
|
||||||
s.file = NewStateFile()
|
s.file = NewStateFile()
|
||||||
@ -389,23 +398,25 @@ func (s *Filesystem) Unlock(id string) error {
|
|||||||
} else {
|
} else {
|
||||||
log.Printf("[TRACE] statemgr.Filesystem: removed lock metadata file %s", lockInfoPath)
|
log.Printf("[TRACE] statemgr.Filesystem: removed lock metadata file %s", lockInfoPath)
|
||||||
}
|
}
|
||||||
fileName := s.stateFileOut.Name()
|
|
||||||
|
|
||||||
unlockErr := s.unlock()
|
unlockErr := s.unlock()
|
||||||
|
// Perform cleanup if stateFileOut is not nil.
|
||||||
|
if s.stateFileOut != nil {
|
||||||
|
fileName := s.stateFileOut.Name()
|
||||||
|
|
||||||
s.stateFileOut.Close()
|
s.stateFileOut.Close()
|
||||||
s.stateFileOut = nil
|
s.stateFileOut = nil
|
||||||
s.lockID = ""
|
s.lockID = ""
|
||||||
|
|
||||||
// clean up the state file if we created it an never wrote to it
|
// Clean up the state file if we created it and never wrote to it
|
||||||
stat, err := os.Stat(fileName)
|
stat, err := os.Stat(fileName)
|
||||||
if err == nil && stat.Size() == 0 && s.created {
|
if err == nil && stat.Size() == 0 && s.created {
|
||||||
err = os.Remove(fileName)
|
err = os.Remove(fileName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] stagemgr.Filesystem: error removing empty state file %q: %s", fileName, err)
|
log.Printf("[ERROR] stagemgr.Filesystem: error removing empty state file %q: %s", fileName, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
s.lockID = ""
|
||||||
return unlockErr
|
return unlockErr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,19 @@ func (s *Filesystem) lock() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Filesystem) unlock() error {
|
func (s *Filesystem) unlock() error {
|
||||||
|
// Handle case where s.stateFileOut is nil, indicating no lock to release.
|
||||||
|
if s.stateFileOut == nil {
|
||||||
|
log.Print("[TRACE] statemgr.Filesystem: statefileout is nil, cannot unlock")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if file descriptor is invalid
|
||||||
|
fd := s.stateFileOut.Fd()
|
||||||
|
if fd == ^uintptr(0) {
|
||||||
|
log.Print("[TRACE] statemgr.Filesystem: fd is invalid, cannot unlock")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
log.Printf("[TRACE] statemgr.Filesystem: unlocking %s using fcntl flock", s.path)
|
log.Printf("[TRACE] statemgr.Filesystem: unlocking %s using fcntl flock", s.path)
|
||||||
flock := &syscall.Flock_t{
|
flock := &syscall.Flock_t{
|
||||||
Type: syscall.F_UNLCK,
|
Type: syscall.F_UNLCK,
|
||||||
@ -38,6 +51,5 @@ func (s *Filesystem) unlock() error {
|
|||||||
Len: 0,
|
Len: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
fd := s.stateFileOut.Fd()
|
|
||||||
return syscall.FcntlFlock(fd, syscall.F_SETLK, flock)
|
return syscall.FcntlFlock(fd, syscall.F_SETLK, flock)
|
||||||
}
|
}
|
||||||
|
@ -189,8 +189,6 @@ func TestFilesystem_backup(t *testing.T) {
|
|||||||
// not the contents of the input file (which is left unchanged).
|
// not the contents of the input file (which is left unchanged).
|
||||||
func TestFilesystem_backupAndReadPath(t *testing.T) {
|
func TestFilesystem_backupAndReadPath(t *testing.T) {
|
||||||
defer testOverrideVersion(t, "1.2.3")()
|
defer testOverrideVersion(t, "1.2.3")()
|
||||||
info := NewLockInfo()
|
|
||||||
info.Operation = "test"
|
|
||||||
|
|
||||||
workDir := t.TempDir()
|
workDir := t.TempDir()
|
||||||
markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
|
markerOutput := addrs.OutputValue{Name: "foo"}.Absolute(addrs.RootModuleInstance)
|
||||||
@ -256,15 +254,6 @@ func TestFilesystem_backupAndReadPath(t *testing.T) {
|
|||||||
t.Fatalf("failed to write new state: %s", err)
|
t.Fatalf("failed to write new state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockID, err := ls.Lock(info)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ls.Unlock(lockID); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The backup functionality should've saved a copy of the original contents
|
// The backup functionality should've saved a copy of the original contents
|
||||||
// of the _output_ file, even though the first snapshot was read from
|
// of the _output_ file, even though the first snapshot was read from
|
||||||
// the _input_ file.
|
// the _input_ file.
|
||||||
|
Loading…
Reference in New Issue
Block a user