You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/wordkey/generate_test.go

196 lines
6.0 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

package wordkey
import (
"bytes"
"crypto/sha256"
"fmt"
"math"
"strings"
"testing"
)
// The built-in lists and their SHA-256, as lists/README.md records them. A
// change of a list changes the hash an app pins, so it is never silent.
var listHashes = map[string]string{
"es": "ff77b487765c000da97cca58fe94a2cdb947303e7a07460614d7d95d800034fe",
}
func TestBuiltInLists(t *testing.T) {
if got := strings.Join(Languages(), " "); got != "es" {
t.Fatalf("Languages() = %q", got)
}
for lang, want := range listHashes {
if got := fmt.Sprintf("%x", sha256.Sum256([]byte(lists[lang]))); got != want {
t.Errorf("list %s: SHA-256 %s, want %s", lang, got, want)
}
words, err := List(lang)
if err != nil {
t.Fatal(err)
}
if len(words) != 7776 {
t.Errorf("list %s: %d words, want 7776", lang, len(words))
}
}
if _, err := List("xx"); err == nil || !strings.Contains(err.Error(), `no word list for "xx"; the lists are es`) {
t.Errorf("List(xx): %v", err)
}
}
func TestCheckList(t *testing.T) {
// pal followed by three letters: palaaa, palaab, …
base := make([]string, MinListSize)
for i := range base {
base[i] = "pal" + string([]rune{'a' + rune(i/676), 'a' + rune(i/26%26), 'a' + rune(i%26)})
}
if err := CheckList("es", base); err != nil {
t.Fatal(err)
}
if err := CheckList("xx", base); err == nil || err.Error() != `no alphabet for the language "xx"` {
t.Errorf("CheckList(xx): %v", err)
}
with := func(i int, w string) []string {
l := append([]string(nil), base...)
l[i] = w
return l
}
for _, c := range []struct {
list []string
want string
}{
{base[:MinListSize-1], "2047 words, fewer than 2048"},
{with(5, "dos palabras"), `line 6, "dos palabras", is not one word`},
{with(5, " "), "is not one word"},
{with(5, "mi"), `"mi", is not one word of 3 or more letters`},
{with(5, "casa​"), "invisible character U+200B"},
// Only the letters of the alphabet of the language, as the list
// writes them: no capitals, no Cyrillic U+0441 that looks like a Latin
// c, no digits, no carriage return of a file with CRLF lines.
{with(5, "Palaaf"), `line 6, "Palaaf", holds U+0050, which is not in the alphabet of "es"`},
{with(5, string(rune(0x0441))+"asa"), "holds U+0441, which is not in the alphabet"},
{with(5, "pal1"), "holds U+0031"},
{with(5, "palaaf\r"), `line 6, "palaaf\r", holds U+000D`},
{with(5, base[4]), `line 6, "palaae", is the same word as "palaae"`},
{with(5, "palaáe"), `line 6, "palaáe", is the same word as "palaae"`},
{append(with(0, "papá"), "papa"), `"papa", is the same word as "papá"`},
} {
if err := CheckList("es", c.list); err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("CheckList: %v, want %q", err, c.want)
}
}
}
func TestGenerate(t *testing.T) {
list, err := List("es")
if err != nil {
t.Fatal(err)
}
words, err := Generate(list, DefaultCount, nil)
if err != nil {
t.Fatal(err)
}
if len(words) != DefaultCount {
t.Fatalf("%d words", len(words))
}
if err := Check(Normalize(strings.Join(words, " "))); err != nil {
t.Errorf("generated words %q: %v", words, err)
}
seen := map[string]bool{}
for _, w := range words {
if seen[w] {
t.Errorf("%q drawn twice", w)
}
seen[w] = true
}
// The same random bytes draw the same words: Generate reads nothing else.
var seed []byte
for i := range 8 {
h := sha256.Sum256([]byte{byte(i)})
seed = append(seed, h[:]...)
}
a, err := Generate(list, 6, bytes.NewReader(seed))
if err != nil {
t.Fatal(err)
}
b, err := Generate(list, 6, bytes.NewReader(seed))
if err != nil || len(a) != 6 || strings.Join(a, " ") != strings.Join(b, " ") {
t.Errorf("%q and %q: %v", a, b, err)
}
// An index drawn twice is drawn again: each pair of bytes is an index
// below 8192, and 0 1 is index 1.
again := []byte{0, 1, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6}
if w, err := Generate(list, 6, bytes.NewReader(again)); err != nil || strings.Join(w, " ") != strings.Join(list[1:7], " ") {
t.Errorf("Generate with index 1 twice: %q, %v", w, err)
}
// Bytes that only ever give two indices, 1793 and 2081, run out.
if _, err := Generate(list, 6, bytes.NewReader(bytes.Repeat([]byte{7, 1, 200, 33}, 64))); err == nil || !strings.Contains(err.Error(), "EOF") {
t.Errorf("Generate with two indices: %v", err)
}
for _, c := range []struct {
n int
want string
}{
{5, "at least 6 words, not 5"},
{len(list), "7776 words of a list of 7776"},
} {
if _, err := Generate(list, c.n, nil); err == nil || !strings.Contains(err.Error(), c.want) {
t.Errorf("Generate(%d): %v, want %q", c.n, err, c.want)
}
}
if _, err := Generate(list, 6, bytes.NewReader(nil)); err == nil || !strings.Contains(err.Error(), "EOF") {
t.Errorf("Generate without random bytes: %v", err)
}
}
func TestBits(t *testing.T) {
if b := Bits(7776, 1); b != math.Log2(7776) {
t.Errorf("Bits(7776, 1) = %v", b)
}
// 7 words of 7776 are a little under 90.5 bits, and 6 of 2048, the
// fewest that Generate draws, a little under 66.
for _, c := range []struct {
size, count int
low, high float64
}{
{7776, 0, 0, 0},
{7776, 7, 90.469, 90.470},
{7776, 8, 103.393, 103.394},
{2048, 6, 65.989, 65.990},
} {
if b := Bits(c.size, c.count); b < c.low || b > c.high {
t.Errorf("Bits(%d, %d) = %v", c.size, c.count, b)
}
}
}
// Every word is about as likely: over 7776·40 draws of one word, each index
// falls in its bucket of 64 between 0.8 and 1.2 times the mean.
func TestGenerateUniform(t *testing.T) {
if testing.Short() {
t.Skip("slow")
}
list, _ := List("es")
index := make(map[string]int, len(list))
for i, w := range list {
index[w] = i
}
const buckets = 64
var count [buckets]int
draws := 0
for draws < len(list)*40 {
words, err := Generate(list, 6, nil)
if err != nil {
t.Fatal(err)
}
for _, w := range words {
count[index[w]*buckets/len(list)]++
draws++
}
}
mean := float64(draws) / buckets
for i, c := range count {
if float64(c) < 0.8*mean || float64(c) > 1.2*mean {
t.Errorf("bucket %d: %d draws, mean %.0f", i, c, mean)
}
}
}

Powered by TurnKey Linux.