vitess.io/vitess@v0.16.2/go/streamlog/streamlog_flaky_test.go (about)

     1  /*
     2  Copyright 2019 The Vitess 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 streamlog
    18  
    19  import (
    20  	"bufio"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"net/http"
    25  	"net/url"
    26  	"os"
    27  	"path"
    28  	"syscall"
    29  	"testing"
    30  	"time"
    31  )
    32  
    33  type logMessage struct {
    34  	val string
    35  }
    36  
    37  func (l *logMessage) Format(params url.Values) string {
    38  	return l.val + "\n"
    39  }
    40  
    41  func testLogf(w io.Writer, params url.Values, m any) error {
    42  	_, err := io.WriteString(w, m.(*logMessage).Format(params))
    43  	return err
    44  }
    45  
    46  func TestHTTP(t *testing.T) {
    47  	l, err := net.Listen("tcp", "localhost:0")
    48  	if err != nil {
    49  		t.Fatal(err)
    50  	}
    51  	defer l.Close()
    52  	addr := l.Addr().String()
    53  
    54  	go http.Serve(l, nil)
    55  
    56  	logger := New("logger", 1)
    57  	logger.ServeLogs("/log", testLogf)
    58  
    59  	// This should not block - there are no subscribers yet.
    60  	logger.Send(&logMessage{"val1"})
    61  
    62  	// Subscribe.
    63  	resp, err := http.Get(fmt.Sprintf("http://%s/log", addr))
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  	defer func() {
    68  		if resp != nil {
    69  			resp.Body.Close()
    70  		}
    71  	}()
    72  	body := bufio.NewReader(resp.Body)
    73  	if sz := len(logger.subscribed); sz != 1 {
    74  		t.Errorf("want 1, got %d", sz)
    75  	}
    76  
    77  	// Send some messages.
    78  	for i := 0; i < 10; i++ {
    79  		msg := fmt.Sprint("msg", i)
    80  		logger.Send(&logMessage{msg})
    81  		val, err := body.ReadString('\n')
    82  		if err != nil {
    83  			t.Fatal(err)
    84  		}
    85  		if i == 0 && val == "val1\n" {
    86  			// the value that was sent has been in the
    87  			// channels and was actually processed after the
    88  			// subscription took effect. This is fine.
    89  			val, err = body.ReadString('\n')
    90  			if err != nil {
    91  				t.Fatal(err)
    92  			}
    93  		}
    94  		if want := msg + "\n"; val != want {
    95  			t.Errorf("want %q, got %q", msg, val)
    96  		}
    97  	}
    98  
    99  	// Shutdown.
   100  	resp.Body.Close()
   101  	resp = nil
   102  	body = nil
   103  
   104  	// Due to multiple layers of buffering in http.Server, we must
   105  	// send multiple messages to detect the client has gone away.
   106  	// 4 seems to be a minimum, but doesn't always work. So 10 it is.
   107  	logger.mu.Lock()
   108  	if want, got := 1, len(logger.subscribed); want != got {
   109  		t.Errorf("len(logger.subscribed) = %v, want %v", got, want)
   110  	}
   111  	logger.mu.Unlock()
   112  	for i := 0; i < 10; i++ {
   113  		logger.Send(&logMessage{"val3"})
   114  		// Allow time for propagation (loopback interface - expected to be fast).
   115  		time.Sleep(1 * time.Millisecond)
   116  	}
   117  	logger.mu.Lock()
   118  	if want, got := 0, len(logger.subscribed); want != got {
   119  		t.Errorf("len(logger.subscribed) = %v, want %v", got, want)
   120  	}
   121  	logger.mu.Unlock()
   122  }
   123  
   124  func TestChannel(t *testing.T) {
   125  	logger := New("logger", 1)
   126  
   127  	// Subscribe.
   128  	ch := logger.Subscribe("test")
   129  	defer func() {
   130  		if ch != nil {
   131  			logger.Unsubscribe(ch)
   132  		}
   133  	}()
   134  	if sz := len(logger.subscribed); sz != 1 {
   135  		t.Errorf("want 1, got %d", sz)
   136  	}
   137  
   138  	// Send/receive some messages, one at a time.
   139  	for i := 0; i < 10; i++ {
   140  		msg := fmt.Sprint("msg", i)
   141  		done := make(chan struct{})
   142  		go func() {
   143  			if want, got := msg+"\n", (<-ch).(*logMessage).Format(nil); got != want {
   144  				t.Errorf("Unexpected message in log. got: %q, want: %q", got, want)
   145  			}
   146  			close(done)
   147  		}()
   148  		logger.Send(&logMessage{msg})
   149  		<-done
   150  	}
   151  
   152  	// Send/receive many messages with asynchronous writer/reader.
   153  	want := []string{"msg0", "msg1", "msg2", "msg3", "msg4", "msg5"}
   154  	got := make([]string, 0, len(want))
   155  	readDone := make(chan struct{})
   156  	writeDone := make(chan struct{})
   157  	go func() {
   158  		for {
   159  			select {
   160  			case msg := <-ch:
   161  				got = append(got, msg.(*logMessage).Format(nil))
   162  			case <-writeDone:
   163  				close(readDone)
   164  				return
   165  			}
   166  		}
   167  	}()
   168  	for _, x := range want {
   169  		logger.Send(&logMessage{x})
   170  		// Allow propagation delay (cpu/memory-bound - expected to be very fast).
   171  		time.Sleep(1 * time.Millisecond)
   172  	}
   173  	close(writeDone)
   174  	<-readDone
   175  	if len(got) != len(want) {
   176  		t.Errorf("Bad results length: got %d, want %d", len(got), len(want))
   177  	} else {
   178  		for i := 0; i < len(want); i++ {
   179  			if want[i]+"\n" != got[i] {
   180  				t.Errorf("Unexpected result in log: got %q, want %q", got[i], want[i]+"\n")
   181  			}
   182  		}
   183  	}
   184  
   185  	// Shutdown.
   186  	logger.Unsubscribe(ch)
   187  	ch = nil
   188  	if sz := len(logger.subscribed); sz != 0 {
   189  		t.Errorf("want 0, got %d", sz)
   190  	}
   191  }
   192  
   193  func TestFile(t *testing.T) {
   194  	logger := New("logger", 10)
   195  
   196  	dir := t.TempDir()
   197  
   198  	logPath := path.Join(dir, "test.log")
   199  	logChan, err := logger.LogToFile(logPath, testLogf)
   200  	defer logger.Unsubscribe(logChan)
   201  	if err != nil {
   202  		t.Errorf("error enabling file logger: %v", err)
   203  	}
   204  
   205  	logger.Send(&logMessage{"test 1"})
   206  	logger.Send(&logMessage{"test 2"})
   207  
   208  	// Allow time for propagation
   209  	time.Sleep(10 * time.Millisecond)
   210  
   211  	want := "test 1\ntest 2\n"
   212  	contents, _ := os.ReadFile(logPath)
   213  	got := string(contents)
   214  	if want != string(got) {
   215  		t.Errorf("streamlog file: want %q got %q", want, got)
   216  	}
   217  
   218  	// Rename and send another log which should go to the renamed file
   219  	rotatedPath := path.Join(dir, "test.log.1")
   220  	os.Rename(logPath, rotatedPath)
   221  
   222  	logger.Send(&logMessage{"test 3"})
   223  	time.Sleep(10 * time.Millisecond)
   224  
   225  	want = "test 1\ntest 2\ntest 3\n"
   226  	contents, _ = os.ReadFile(rotatedPath)
   227  	got = string(contents)
   228  	if want != string(got) {
   229  		t.Errorf("streamlog file: want %q got %q", want, got)
   230  	}
   231  
   232  	// Send the rotate signal which should reopen the original file path
   233  	// for new logs to go to
   234  	if err := syscall.Kill(syscall.Getpid(), syscall.SIGUSR2); err != nil {
   235  		t.Logf("failed to send streamlog rotate signal: %v", err)
   236  	}
   237  	time.Sleep(10 * time.Millisecond)
   238  
   239  	logger.Send(&logMessage{"test 4"})
   240  	time.Sleep(10 * time.Millisecond)
   241  
   242  	want = "test 1\ntest 2\ntest 3\n"
   243  	contents, _ = os.ReadFile(rotatedPath)
   244  	got = string(contents)
   245  	if want != string(got) {
   246  		t.Errorf("streamlog file: want %q got %q", want, got)
   247  	}
   248  
   249  	want = "test 4\n"
   250  	contents, _ = os.ReadFile(logPath)
   251  	got = string(contents)
   252  	if want != string(got) {
   253  		t.Errorf("streamlog file: want %q got %q", want, got)
   254  	}
   255  }