/*
Copyright (c) 2019 Ben Morrison (gbmor)
This file is part of Registry.
Registry is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Registry is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Registry. If not, see <https://www.gnu.org/licenses/>.
*/
package registry
import (
"bufio"
"os"
"strings"
"testing"
"time"
)
var queryUserCases = []struct {
name string
term string
wantErr bool
}{
{
name: "Valid User",
term: "foo",
wantErr: false,
},
{
name: "Empty Query",
term: "",
wantErr: false,
},
{
name: "Nonexistent User",
term: "doesntexist",
wantErr: true,
},
{
name: "Garbage Data",
term: "will be replaced with garbage data",
wantErr: true,
},
}
// Checks if Registry.QueryUser() returns users that
// match the provided substring.
func Test_Registry_QueryUser(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryUserCases[3].term = string(buf)
for n, tt := range queryUserCases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryUser(tt.term)
if out == nil && err != nil && !tt.wantErr {
t.Errorf("Received nil output or an error when unexpected. Case %v, %v, %v\n", n, tt.term, err)
}
if out != nil && tt.wantErr {
t.Errorf("Received unexpected nil output when an error was expected. Case %v, %v\n", n, tt.term)
}
for _, e := range out {
one := strings.Split(e, "\t")
if !strings.Contains(one[0], tt.term) && !strings.Contains(one[1], tt.term) {
t.Errorf("Received incorrect output: %v != %v\n", tt.term, e)
}
}
})
}
}
func Benchmark_Registry_QueryUser(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range queryUserCases {
_, err := registry.QueryUser(tt.term)
if err != nil {
b.Errorf("%v\n", err)
}
}
}
}
var queryInStatusCases = []struct {
name string
substr string
wantNil bool
wantErr bool
}{
{
name: "Tag in Status",
substr: "twtxt",
wantNil: false,
wantErr: false,
},
{
name: "Valid URL",
substr: "https://example.com/twtxt.txt",
wantNil: false,
wantErr: false,
},
{
name: "Multiple Words in Status",
substr: "next programming",
wantNil: false,
wantErr: false,
},
{
name: "Multiple Words, Not in Status",
substr: "explosive bananas from antarctica",
wantNil: true,
wantErr: false,
},
{
name: "Empty Query",
substr: "",
wantNil: true,
wantErr: true,
},
{
name: "Nonsense",
substr: "ahfiurrenkhfkajdhfao",
wantNil: true,
wantErr: false,
},
{
name: "Invalid URL",
substr: "https://doesnt.exist/twtxt.txt",
wantNil: true,
wantErr: false,
},
{
name: "Garbage Data",
substr: "will be replaced with garbage data",
wantNil: true,
wantErr: false,
},
}
// This tests whether we can find a substring in all of
// the known status messages, disregarding the metadata
// stored with each status.
func Test_Registry_QueryInStatus(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryInStatusCases[7].substr = string(buf)
for _, tt := range queryInStatusCases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryInStatus(tt.substr)
if err != nil && !tt.wantErr {
t.Errorf("Caught unexpected error: %v\n", err)
}
if !tt.wantErr && out == nil && !tt.wantNil {
t.Errorf("Got nil when expecting output\n")
}
if err == nil && tt.wantErr {
t.Errorf("Expecting error, got nil.\n")
}
for _, e := range out {
split := strings.Split(strings.ToLower(e), "\t")
if e != "" {
if !strings.Contains(split[3], strings.ToLower(tt.substr)) {
t.Errorf("Status without substring returned\n")
}
}
}
})
}
}
func Benchmark_Registry_QueryInStatus(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range queryInStatusCases {
_, err := registry.QueryInStatus(tt.substr)
if err != nil {
continue
}
}
}
}
// Tests whether we can retrieve the 20 most
// recent statuses in the registry
func Test_QueryAllStatuses(t *testing.T) {
registry := initTestEnv()
t.Run("Latest Statuses", func(t *testing.T) {
out, err := registry.QueryAllStatuses()
if out == nil || err != nil {
t.Errorf("Got no statuses, or more than 20: %v, %v\n", len(out), err)
}
})
}
func Benchmark_QueryAllStatuses(b *testing.B) {
registry := initTestEnv()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := registry.QueryAllStatuses()
if err != nil {
continue
}
}
}
var get20cases = []struct {
name string
page int
wantErr bool
}{
{
name: "First Page",
page: 1,
wantErr: false,
},
{
name: "High Page Number",
page: 256,
wantErr: false,
},
{
name: "Illegal Page Number",
page: -23,
wantErr: false,
},
}
func Test_ReduceToPage(t *testing.T) {
registry := initTestEnv()
for _, tt := range get20cases {
t.Run(tt.name, func(t *testing.T) {
out, err := registry.QueryAllStatuses()
if err != nil && !tt.wantErr {
t.Errorf("%v\n", err.Error())
}
out = ReduceToPage(tt.page, out)
if len(out) > 20 || len(out) == 0 {
t.Errorf("Page-Reduce Malfunction: length of data %v\n", len(out))
}
})
}
}
func Benchmark_ReduceToPage(b *testing.B) {
registry := initTestEnv()
out, _ := registry.QueryAllStatuses()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range get20cases {
ReduceToPage(tt.page, out)
}
}
}
// This tests whether we can find a substring in the
// given user's status messages, disregarding the metadata
// stored with each status.
func Test_User_FindInStatus(t *testing.T) {
registry := initTestEnv()
var buf = make([]byte, 256)
// read random data into case 8
rando, _ := os.Open("/dev/random")
reader := bufio.NewReader(rando)
n, err := reader.Read(buf)
if err != nil || n == 0 {
t.Errorf("Couldn't set up test: %v\n", err)
}
queryInStatusCases[7].substr = string(buf)
data := make([]*User, 0)
for _, v := range registry.Users {
data = append(data, v)
}
for _, tt := range queryInStatusCases {
t.Run(tt.name, func(t *testing.T) {
for _, e := range data {
tag := e.FindInStatus(tt.substr)
if tag == nil && !tt.wantNil {
t.Errorf("Got nil tag\n")
}
}
})
}
}
func Benchmark_User_FindInStatus(b *testing.B) {
registry := initTestEnv()
data := make([]*User, 0)
for _, v := range registry.Users {
data = append(data, v)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
for _, tt := range data {
for _, v := range queryInStatusCases {
tt.FindInStatus(v.substr)
}
}
}
}
func Test_SortByTime_Slice(t *testing.T) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
t.Errorf("Failed to finish test initialization: %v\n", err)
}
t.Run("Sort By Time ([]TimeMap)", func(t *testing.T) {
sorted, err := SortByTime(statusmap)
if err != nil {
t.Errorf("%v\n", err)
}
split := strings.Split(sorted[0], "\t")
firsttime, _ := time.Parse("RFC3339", split[0])
for i := range sorted {
if i < len(sorted)-1 {
nextsplit := strings.Split(sorted[i+1], "\t")
nexttime, _ := time.Parse("RFC3339", nextsplit[0])
if firsttime.Before(nexttime) {
t.Errorf("Timestamps out of order: %v\n", sorted)
}
firsttime = nexttime
}
}
})
}
// Benchmarking a sort of 1000000 statuses by timestamp.
// Right now it's at roughly 2000ns per 2 statuses.
// Set sortMultiplier to be the number of desired
// statuses divided by four.
func Benchmark_SortByTime_Slice(b *testing.B) {
// I set this to 250,000,000 and it hard-locked
// my laptop. Oops.
sortMultiplier := 250
b.Logf("Benchmarking SortByTime with a constructed slice of %v statuses ...\n", sortMultiplier*4)
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
b.Errorf("Failed to finish benchmark initialization: %v\n", err)
}
// Constructed registry has four statuses. This
// makes a TimeMapSlice of 1000000 statuses.
statusmaps := make([]TimeMap, sortMultiplier*4)
for i := 0; i < sortMultiplier; i++ {
statusmaps = append(statusmaps, statusmap)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := SortByTime(statusmaps...)
if err != nil {
b.Errorf("%v\n", err)
}
}
}
func Test_SortByTime_Single(t *testing.T) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
t.Errorf("Failed to finish test initialization: %v\n", err)
}
t.Run("Sort By Time (TimeMap)", func(t *testing.T) {
sorted, err := SortByTime(statusmap)
if err != nil {
t.Errorf("%v\n", err)
}
split := strings.Split(sorted[0], "\t")
firsttime, _ := time.Parse("RFC3339", split[0])
for i := range sorted {
if i < len(sorted)-1 {
nextsplit := strings.Split(sorted[i+1], "\t")
nexttime, _ := time.Parse("RFC3339", nextsplit[0])
if firsttime.Before(nexttime) {
t.Errorf("Timestamps out of order: %v\n", sorted)
}
firsttime = nexttime
}
}
})
}
func Benchmark_SortByTime_Single(b *testing.B) {
registry := initTestEnv()
statusmap, err := registry.GetStatuses()
if err != nil {
b.Errorf("Failed to finish benchmark initialization: %v\n", err)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := SortByTime(statusmap)
if err != nil {
b.Errorf("%v\n", err)
}
}
}