go4.org@v0.0.0-20230225012048-214862532bf5/lock/lock_test.go (about)

     1  /*
     2  Copyright 2013 The Go Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8       http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package lock
    18  
    19  import (
    20  	"bufio"
    21  	"errors"
    22  	"fmt"
    23  	"io"
    24  	"io/ioutil"
    25  	"os"
    26  	"os/exec"
    27  	"path/filepath"
    28  	"strconv"
    29  	"testing"
    30  )
    31  
    32  func TestLock(t *testing.T) {
    33  	testLock(t, false)
    34  }
    35  
    36  func TestLockPortable(t *testing.T) {
    37  	testLock(t, true)
    38  }
    39  
    40  func TestLockInChild(t *testing.T) {
    41  	f := os.Getenv("TEST_LOCK_FILE")
    42  	if f == "" {
    43  		// not child
    44  		return
    45  	}
    46  	lock := Lock
    47  	if v, _ := strconv.ParseBool(os.Getenv("TEST_LOCK_PORTABLE")); v {
    48  		lock = lockPortable
    49  	}
    50  
    51  	var lk io.Closer
    52  	for scan := bufio.NewScanner(os.Stdin); scan.Scan(); {
    53  		var err error
    54  		switch scan.Text() {
    55  		case "lock":
    56  			lk, err = lock(f)
    57  		case "unlock":
    58  			err = lk.Close()
    59  			lk = nil
    60  		case "exit":
    61  			// Simulate a crash, or at least not unlocking the lock.
    62  			os.Exit(0)
    63  		default:
    64  			err = fmt.Errorf("unexpected child command %q", scan.Text())
    65  		}
    66  		if err != nil {
    67  			fmt.Println(err)
    68  		} else {
    69  			fmt.Println("")
    70  		}
    71  	}
    72  }
    73  
    74  func testLock(t *testing.T, portable bool) {
    75  	lock := Lock
    76  	if portable {
    77  		lock = lockPortable
    78  	}
    79  	t.Logf("test lock, portable %v", portable)
    80  
    81  	td, err := ioutil.TempDir("", "")
    82  	if err != nil {
    83  		t.Fatal(err)
    84  	}
    85  	defer os.RemoveAll(td)
    86  
    87  	path := filepath.Join(td, "foo.lock")
    88  
    89  	proc := newChildProc(t, path, portable)
    90  	defer proc.kill()
    91  
    92  	t.Logf("First lock in child")
    93  	if err := proc.do("lock"); err != nil {
    94  		t.Fatalf("first lock in child process: %v", err)
    95  	}
    96  
    97  	t.Logf("Crash child")
    98  	if err := proc.do("exit"); err != nil {
    99  		t.Fatalf("crash in child process: %v", err)
   100  	}
   101  
   102  	proc = newChildProc(t, path, portable)
   103  	defer proc.kill()
   104  
   105  	t.Logf("Locking+unlocking in child...")
   106  	if err := proc.do("lock"); err != nil {
   107  		t.Fatalf("lock in child process after crashing child: %v", err)
   108  	}
   109  	if err := proc.do("unlock"); err != nil {
   110  		t.Fatalf("lock in child process after crashing child: %v", err)
   111  	}
   112  
   113  	t.Logf("Locking in parent...")
   114  	lk1, err := lock(path)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  
   119  	t.Logf("Again in parent...")
   120  	_, err = lock(path)
   121  	if err == nil {
   122  		t.Fatal("expected second lock to fail")
   123  	}
   124  
   125  	t.Logf("Locking in child...")
   126  	if err := proc.do("lock"); err == nil {
   127  		t.Fatalf("expected lock in child process to fail")
   128  	}
   129  
   130  	t.Logf("Unlocking lock in parent")
   131  	if err := lk1.Close(); err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	t.Logf("Trying lock again in child...")
   136  	if err := proc.do("lock"); err != nil {
   137  		t.Fatal(err)
   138  	}
   139  	if err := proc.do("unlock"); err != nil {
   140  		t.Fatal(err)
   141  	}
   142  
   143  	lk3, err := lock(path)
   144  	if err != nil {
   145  		t.Fatal(err)
   146  	}
   147  	lk3.Close()
   148  }
   149  
   150  type childLockCmd struct {
   151  	op    string
   152  	reply chan<- error
   153  }
   154  
   155  type childProc struct {
   156  	proc *os.Process
   157  	c    chan childLockCmd
   158  }
   159  
   160  func (c *childProc) kill() {
   161  	c.proc.Kill()
   162  }
   163  
   164  func (c *childProc) do(op string) error {
   165  	reply := make(chan error)
   166  	c.c <- childLockCmd{
   167  		op:    op,
   168  		reply: reply,
   169  	}
   170  	return <-reply
   171  }
   172  
   173  func newChildProc(t *testing.T, path string, portable bool) *childProc {
   174  	cmd := exec.Command(os.Args[0], "-test.run=LockInChild$")
   175  	cmd.Env = []string{"TEST_LOCK_FILE=" + path}
   176  	toChild, err := cmd.StdinPipe()
   177  	if err != nil {
   178  		t.Fatalf("cannot make pipe: %v", err)
   179  	}
   180  	fromChild, err := cmd.StdoutPipe()
   181  	if err != nil {
   182  		t.Fatalf("cannot make pipe: %v", err)
   183  	}
   184  	cmd.Stderr = os.Stderr
   185  	if portable {
   186  		cmd.Env = append(cmd.Env, "TEST_LOCK_PORTABLE=1")
   187  	}
   188  	if err := cmd.Start(); err != nil {
   189  		t.Fatalf("cannot start child: %v", err)
   190  	}
   191  	cmdChan := make(chan childLockCmd)
   192  	go func() {
   193  		defer fromChild.Close()
   194  		defer toChild.Close()
   195  		inScan := bufio.NewScanner(fromChild)
   196  		for c := range cmdChan {
   197  			fmt.Fprintln(toChild, c.op)
   198  			ok := inScan.Scan()
   199  			if c.op == "exit" {
   200  				if ok {
   201  					c.reply <- errors.New("child did not exit")
   202  				} else {
   203  					cmd.Wait()
   204  					c.reply <- nil
   205  				}
   206  				break
   207  			}
   208  			if !ok {
   209  				panic("child exited early")
   210  			}
   211  			if errText := inScan.Text(); errText != "" {
   212  				c.reply <- errors.New(errText)
   213  			} else {
   214  				c.reply <- nil
   215  			}
   216  		}
   217  	}()
   218  	return &childProc{
   219  		c:    cmdChan,
   220  		proc: cmd.Process,
   221  	}
   222  }