blob: f4c8f40fc5e67746e08565a6f1d5907c092c1167 [file] [log] [blame]
// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package test
// This file tests the DirServer Watch API. It only works on implementations
// that support Watch; on others it simply skips this test.
import (
"testing"
"upspin.io/errors"
"upspin.io/test/testenv"
"upspin.io/upspin"
)
// watchSupported checks for an error after a call to Watch, and if
// there is an ErrNotSupported error, returns false. It returns true
// if there was no error; otherwise it fatals.
func watchSupported(t *testing.T, r *testenv.Runner) bool {
t.Helper()
supported, err := watchNotSupportedError(t, r)
if err != nil {
t.Fatal(err)
}
return supported
}
// watchSupported checks for an error after a call to Watch, and if
// there is an ErrNotSupported error, returns false. It returns true
// if there was no error or the error is not ErrNotSupported.
func watchNotSupportedError(t *testing.T, r *testenv.Runner) (bool, error) {
err := r.Err()
if errors.Match(upspin.ErrNotSupported, err) {
t.Log("Watch not supported for this DirServer.")
return false, nil
}
return true, err
}
func watchEventsValid(t *testing.T, r *testenv.Runner) {
t.Helper()
for i, event := range r.Events {
if event.Error != nil {
continue
}
entry := event.Entry
if entry == nil {
t.Fatalf("nil entry in event %d", i)
}
}
}
func testWatchCurrent(t *testing.T, r *testenv.Runner) {
const (
hasBlocks = true
base = ownerName + "/watch-test"
file = base + "/testfile"
access = base + "/Access"
accessContent = "*: " + ownerName
)
r.As(ownerName)
r.MakeDirectory(base)
r.Put(file, "something")
if r.Failed() {
t.Fatal(r.Diag())
}
done := r.DirWatch(base, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
r.GetNEvents(2)
if !r.GotEvent(base, !hasBlocks) {
t.Fatal(r.Diag())
}
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
// Put an Access file; watch it appear on the channel.
r.Put(access, accessContent)
r.GetNEvents(2)
if !r.GotEvent(access, hasBlocks) {
t.Fatal(r.Diag())
}
close(done)
// Reader can set a watcher, but will get no data due to lack of rights.
r.As(readerName)
done = r.DirWatch(base, upspin.WatchCurrent)
if !r.GetErrorEvent(errors.E("no response on event channel after one second")) {
t.Fatal(r.Diag())
}
close(done)
// Allow reader to list, but not read.
r.As(ownerName)
r.Put(access, "l: "+readerName+"\n*:"+ownerName)
r.As(readerName)
done = r.DirWatch(base, upspin.WatchCurrent)
if r.Failed() {
t.Fatal(r.Diag())
}
r.GetNEvents(3)
if !r.GotEvent(base, !hasBlocks) {
t.Fatal(r.Diag())
}
if !r.GotEvent(access, hasBlocks) {
t.Fatal(r.Diag())
}
if !r.GotEvent(file, !hasBlocks) {
t.Fatal(r.Diag())
}
close(done)
if r.GetNEvents(1) {
t.Fatalf("Channel had more events")
}
watchEventsValid(t, r)
}
// Test some error conditions.
func testWatchErrors(t *testing.T, r *testenv.Runner) {
const (
base = ownerName + "/watch-errors"
file = base + "/aFile"
badFile = "nobody@x/foo"
)
r.As(ownerName)
r.MakeDirectory(base)
r.Put(file, "dummy")
if r.Failed() {
t.Fatal(r.Diag())
}
r.DirWatch(base, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
// Should get an error for bad file syntax
r.DirWatch(badFile, 777)
if !r.Failed() {
t.Fatalf("expected Watch error for bad file name %q", badFile)
}
// 777777 is an implausible sequence number, at least in this test.
// TODO: Find a better way to test this.
r.DirWatch(base, 777777)
if r.Failed() {
t.Fatal(r.Diag())
}
if !r.GetErrorEvent(errors.E(errors.Invalid)) {
t.Fatal(r.Diag())
}
}
func testWatchNonExistentFile(t *testing.T, r *testenv.Runner) {
const (
hasBlocks = true
base = ownerName + "/watch-non-existent-file"
file = base + "/aFile"
)
r.As(ownerName)
r.MakeDirectory(base)
// Don't create the file yet.
if r.Failed() {
t.Fatal(r.Diag())
}
r.DirWatch(base, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
r.GetNEvents(1)
// Should see the directory.
if !r.GotEvent(base, !hasBlocks) {
t.Fatal(r.Diag())
}
// Now create the file. Should see it appear.
r.Put(file, "something")
if r.Failed() {
t.Fatal(r.Diag())
}
r.GetNEvents(2)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
}
func testWatchNonExistentDir(t *testing.T, r *testenv.Runner) {
const (
hasBlocks = true
base = ownerName + "/watch-non-existent-dir"
file = base + "/aFile"
)
r.As(ownerName)
// Don't create the dir yet.
r.DirWatch(base, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
// Now create the dir. Should see it appear.
r.MakeDirectory(base)
// Don't create the file yet.
if r.Failed() {
t.Fatal(r.Diag())
}
// Should see the directory.
r.GetNEvents(1)
if !r.GotEvent(base, !hasBlocks) {
t.Fatal(r.Diag())
}
// Now create the file. Should see it appear.
r.Put(file, "something")
if r.Failed() {
t.Fatal(r.Diag())
}
r.GetNEvents(2)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
}
func testWatchForbiddenFile(t *testing.T, r *testenv.Runner) {
const (
hasBlocks = true
base = ownerName + "/watch-forbidden-file"
file = base + "/aFile"
access = base + "/Access"
forbiddenAccessContent = "*: " + ownerName
allowedAccessContent = "*: " + ownerName + " " + readerName
)
r.As(ownerName)
r.MakeDirectory(base)
r.Put(access, forbiddenAccessContent)
r.Put(file, "something")
if r.Failed() {
t.Fatal(r.Diag())
}
// Switch users. Should not see event.
r.As(readerName)
r.DirWatch(file, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
r.GetNEvents(1)
if r.GotEvent(file, hasBlocks) {
t.Fatal("Should not see event for forbidden file")
}
// Now grant permission.
r.As(ownerName)
r.Put(access, allowedAccessContent)
if r.Failed() {
t.Fatal(r.Diag())
}
// Now should see file as other user.
r.As(readerName)
r.DirWatch(file, upspin.WatchCurrent)
r.GetNEvents(1)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
}
func testWatchSubtree(t *testing.T, r *testenv.Runner) {
const (
hasBlocks = true
base = ownerName + "/watch-subtree"
file = base + "/aFile"
dir = base + "/dir"
dirFile = dir + "/file"
)
r.As(ownerName)
r.MakeDirectory(base)
r.MakeDirectory(dir)
if r.Failed() {
t.Fatal(r.Diag())
}
r.DirWatch(dir, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
// Create file in root. Should not see event.
r.Put(file, "something")
r.GetNEvents(1)
if r.GotEvent(file, hasBlocks) {
t.Fatal("Should not see event for parent directory")
}
// Create file in subdir. Should see event.
r.Put(dirFile, "something")
r.GetNEvents(2)
if !r.GotEvent(dirFile, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
}
func testWatchFile(t *testing.T, r *testenv.Runner) {
const (
base = ownerName + "/watch-file"
file = base + "/aFile"
hasBlocks = true
)
r.As(ownerName)
r.MakeDirectory(base)
if r.Failed() {
t.Fatal(r.Diag())
}
r.DirWatch(file, upspin.WatchCurrent)
if !watchSupported(t, r) {
return
}
// Do it all twice to check it works after deletion.
for i := 0; i < 2; i++ {
// Create file. It doesn't exist yet. Should see event.
r.Put(file, "something")
r.GetNEvents(1)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
// Modify file. Should see event.
r.Put(file, "something else")
r.GetNEvents(1)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
// Modify file again. Should see event. (This didn't work at one point.)
r.Put(file, "something else again")
r.GetNEvents(1)
if !r.GotEvent(file, hasBlocks) {
t.Fatal(r.Diag())
}
watchEventsValid(t, r)
// Delete file. Should see event.
r.Delete(file)
r.GetDeleteEvent(file)
if r.Failed() {
t.Fatal(r.Diag())
}
}
}
func testWatchNonExistentRoot(t *testing.T, r *testenv.Runner) {
r.As(ownerName)
r.DirWatch(readerName+"/", upspin.WatchCurrent)
supported, err := watchNotSupportedError(t, r)
if !supported {
return
}
if !errors.Is(errors.NotExist, err) {
t.Fatalf("Expected %v, got %v", errNotExist, err)
}
}