From 5fa82a3abb7585fe240f9bae4807a586df5c7a09 Mon Sep 17 00:00:00 2001 From: mpl Date: Tue, 30 Jul 2013 21:57:50 +0200 Subject: [PATCH] integration tests: kill camlistored when all tests are done -osutil: PollParent to monitor parent process. -camlistored: option to kill itself if it has been orphaned. Change-Id: I87193254d55847e46134439ecd1b04f71718d083 --- pkg/fs/z_test.go | 34 ++++++++++++++++++++++++ pkg/osutil/findproc_appengine.go | 29 +++++++++++++++++++++ pkg/osutil/findproc_normal.go | 43 +++++++++++++++++++++++++++++++ pkg/test/integration/z_test.go | 32 +++++++++++++++++++++++ pkg/test/world.go | 15 ++++++++--- server/camlistored/camlistored.go | 15 +++++++++-- 6 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 pkg/fs/z_test.go create mode 100644 pkg/osutil/findproc_appengine.go create mode 100644 pkg/osutil/findproc_normal.go create mode 100644 pkg/test/integration/z_test.go diff --git a/pkg/fs/z_test.go b/pkg/fs/z_test.go new file mode 100644 index 000000000..bca132e42 --- /dev/null +++ b/pkg/fs/z_test.go @@ -0,0 +1,34 @@ +// +build linux darwin + +/* +Copyright 2013 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fs + +import ( + "testing" + + "camlistore.org/pkg/test" +) + +// Make sure that the camlistored process started +// by the World gets terminated when all the tests +// are done. +// This works only as long as TestZLastTest is the +// last test to run in the package. +func TestZLastTest(t *testing.T) { + test.GetWorldMaybe(t).Stop() +} diff --git a/pkg/osutil/findproc_appengine.go b/pkg/osutil/findproc_appengine.go new file mode 100644 index 000000000..365b69581 --- /dev/null +++ b/pkg/osutil/findproc_appengine.go @@ -0,0 +1,29 @@ +// +build !appengine + +/* +Copyright 2013 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package osutil + +import ( + "log" +) + +func DieOnParentDeath() { + // TODO(mpl): maybe the way it's done in findproc_normal.go actually works + // on appengine too? Verify that. + log.Fatal("DieOnParentDeath not implemented on appengine.") +} diff --git a/pkg/osutil/findproc_normal.go b/pkg/osutil/findproc_normal.go new file mode 100644 index 000000000..abb8e4d61 --- /dev/null +++ b/pkg/osutil/findproc_normal.go @@ -0,0 +1,43 @@ +// +build !appengine + +/* +Copyright 2013 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package osutil + +import ( + "os" + "time" +) + +// DieOnParentDeath starts a goroutine that regularly checks that +// the current process can find its parent, and calls os.Exit(0) +// as soon as it cannot. +func DieOnParentDeath() { + // TODO: on Linux, use PR_SET_PDEATHSIG later. For now, the portable way: + go func() { + pollParent(30 * time.Second) + os.Exit(0) + }() +} + +// pollParent checks every t that the ppid of the current +// process has not changed (i.e that the process has not +// been orphaned). It returns as soon as that ppid changes. +func pollParent(t time.Duration) { + for initial := os.Getppid(); initial == os.Getppid(); time.Sleep(t) { + } +} diff --git a/pkg/test/integration/z_test.go b/pkg/test/integration/z_test.go new file mode 100644 index 000000000..0f657d3d3 --- /dev/null +++ b/pkg/test/integration/z_test.go @@ -0,0 +1,32 @@ +/* +Copyright 2013 The Camlistore Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package integration + +import ( + "testing" + + "camlistore.org/pkg/test" +) + +// Make sure that the camlistored process started +// by the World gets terminated when all the tests +// are done. +// This works only as long as TestZLastTest is the +// last test to run in the package. +func TestZLastTest(t *testing.T) { + test.GetWorldMaybe(t).Stop() +} diff --git a/pkg/test/world.go b/pkg/test/world.go index 4111d3034..44f45101d 100644 --- a/pkg/test/world.go +++ b/pkg/test/world.go @@ -85,12 +85,12 @@ func (w *World) Start() error { { cmd := exec.Command("go", "run", "make.go") cmd.Dir = w.camRoot - log.Printf("Running make.go to build camlistore binaries for testing...") + log.Print("Running make.go to build camlistore binaries for testing...") out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("Error building world: %v, %s", err, string(out)) } - log.Printf("Ran make.go.") + log.Print("Ran make.go.") } // Start camlistored. @@ -99,12 +99,14 @@ func (w *World) Start() error { filepath.Join(w.camRoot, "bin", "camlistored"), "--configfile="+filepath.Join(w.camRoot, "pkg", "test", "testdata", "server-config.json"), "--listen=FD:3", + "--pollparent=true", ) var buf bytes.Buffer w.server.Stdout = &buf w.server.Stderr = &buf w.server.Dir = w.tempDir w.server.Env = append(os.Environ(), + "CAMLI_DEBUG=1", "CAMLI_ROOT="+w.tempDir, "CAMLI_SECRET_RING="+filepath.Join(w.camRoot, filepath.FromSlash("pkg/jsonsign/testdata/test-secring.gpg")), "CAMLI_BASE_URL=http://127.0.0.1:"+strconv.Itoa(w.port), @@ -146,6 +148,9 @@ func (w *World) Start() error { } func (w *World) Stop() { + if w == nil { + return + } w.server.Process.Kill() if d := w.tempDir; d != "" { @@ -153,7 +158,6 @@ func (w *World) Stop() { } } -// func (w *World) Cmd(binary string, args ...string) *exec.Cmd { cmd := exec.Command(filepath.Join(w.camRoot, "bin", binary), args...) switch binary { @@ -199,6 +203,11 @@ func GetWorld(t *testing.T) *World { return w } +// GetWorldMaybe returns the current World. It might be nil. +func GetWorldMaybe(t *testing.T) *World { + return theWorld +} + // RunCmd runs c (which is assumed to be something short-lived, like a // camput or camget command), capturing its stdout for return, and // also capturing its stderr, just in the case of errors. diff --git a/server/camlistored/camlistored.go b/server/camlistored/camlistored.go index 3d129a95e..de1c20b9e 100644 --- a/server/camlistored/camlistored.go +++ b/server/camlistored/camlistored.go @@ -35,6 +35,7 @@ import ( "os/signal" "path/filepath" "runtime" + "strconv" "strings" "syscall" "time" @@ -49,11 +50,11 @@ import ( // Storage options: _ "camlistore.org/pkg/blobserver/cond" _ "camlistore.org/pkg/blobserver/encrypt" + _ "camlistore.org/pkg/blobserver/google" _ "camlistore.org/pkg/blobserver/localdisk" _ "camlistore.org/pkg/blobserver/remote" _ "camlistore.org/pkg/blobserver/replica" _ "camlistore.org/pkg/blobserver/s3" - _ "camlistore.org/pkg/blobserver/google" _ "camlistore.org/pkg/blobserver/shard" // Indexers: (also present themselves as storage targets) _ "camlistore.org/pkg/index" // base indexer + in-memory dev index @@ -76,9 +77,16 @@ var ( flagVersion = flag.Bool("version", false, "show version") flagConfigFile = flag.String("configfile", "", "Config file to use, relative to the Camlistore configuration directory root. If blank, the default is used or auto-generated.") - listenFlag = flag.String("listen", "", "host:port to listen on, or :0 to auto-select. If blank, the value in the config will be used instead.") + listenFlag = flag.String("listen", "", "host:port to listen on, or :0 to auto-select. If blank, the value in the config will be used instead.") + flagPollParent bool ) +func init() { + if debug, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG")); debug { + flag.BoolVar(&flagPollParent, "pollparent", false, "Camlistored regularly polls its parent process to detect if it has been orphaned, and terminates in that case. Mainly useful for tests.") + } +} + func exitf(pattern string, args ...interface{}) { if !strings.HasSuffix(pattern, "\n") { pattern = pattern + "\n" @@ -424,5 +432,8 @@ func main() { go ws.Serve() go handleSignals() + if flagPollParent { + osutil.DieOnParentDeath() + } select {} }