420 lines
9.3 KiB
Go
420 lines
9.3 KiB
Go
|
// +build go1.10
|
||
|
|
||
|
package main
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"log"
|
||
|
"math/rand"
|
||
|
"os"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/ncw/rclone/lib/encoder"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
edgeLeft = iota
|
||
|
edgeRight
|
||
|
)
|
||
|
|
||
|
type mapping struct {
|
||
|
mask uint
|
||
|
src, dst []rune
|
||
|
}
|
||
|
type stringPair struct {
|
||
|
a, b string
|
||
|
}
|
||
|
|
||
|
const header = `// Code generated by ./internal/gen/main.go. DO NOT EDIT.
|
||
|
|
||
|
` + `//go:generate go run ./internal/gen/main.go
|
||
|
|
||
|
package encoder
|
||
|
|
||
|
`
|
||
|
|
||
|
var maskBits = []struct {
|
||
|
mask uint
|
||
|
name string
|
||
|
}{
|
||
|
{encoder.EncodeZero, "EncodeZero"},
|
||
|
{encoder.EncodeWin, "EncodeWin"},
|
||
|
{encoder.EncodeSlash, "EncodeSlash"},
|
||
|
{encoder.EncodeBackSlash, "EncodeBackSlash"},
|
||
|
{encoder.EncodeHashPercent, "EncodeHashPercent"},
|
||
|
{encoder.EncodeDel, "EncodeDel"},
|
||
|
{encoder.EncodeCtl, "EncodeCtl"},
|
||
|
{encoder.EncodeLeftSpace, "EncodeLeftSpace"},
|
||
|
{encoder.EncodeLeftTilde, "EncodeLeftTilde"},
|
||
|
{encoder.EncodeRightSpace, "EncodeRightSpace"},
|
||
|
{encoder.EncodeRightPeriod, "EncodeRightPeriod"},
|
||
|
{encoder.EncodeInvalidUtf8, "EncodeInvalidUtf8"},
|
||
|
}
|
||
|
var edges = []struct {
|
||
|
mask uint
|
||
|
name string
|
||
|
edge int
|
||
|
orig rune
|
||
|
replace rune
|
||
|
}{
|
||
|
{encoder.EncodeLeftSpace, "EncodeLeftSpace", edgeLeft, ' ', '␠'},
|
||
|
{encoder.EncodeLeftTilde, "EncodeLeftTilde", edgeLeft, '~', '~'},
|
||
|
{encoder.EncodeRightSpace, "EncodeRightSpace", edgeRight, ' ', '␠'},
|
||
|
{encoder.EncodeRightPeriod, "EncodeRightPeriod", edgeRight, '.', '.'},
|
||
|
}
|
||
|
|
||
|
var allMappings = []mapping{{
|
||
|
encoder.EncodeZero, []rune{
|
||
|
0,
|
||
|
}, []rune{
|
||
|
'␀',
|
||
|
}}, {
|
||
|
encoder.EncodeWin, []rune{
|
||
|
':', '?', '"', '*', '<', '>', '|',
|
||
|
}, []rune{
|
||
|
':', '?', '"', '*', '<', '>', '|',
|
||
|
}}, {
|
||
|
encoder.EncodeSlash, []rune{
|
||
|
'/',
|
||
|
}, []rune{
|
||
|
'/',
|
||
|
}}, {
|
||
|
encoder.EncodeBackSlash, []rune{
|
||
|
'\\',
|
||
|
}, []rune{
|
||
|
'\',
|
||
|
}}, {
|
||
|
encoder.EncodeHashPercent, []rune{
|
||
|
'#', '%',
|
||
|
}, []rune{
|
||
|
'#', '%',
|
||
|
}}, {
|
||
|
encoder.EncodeDel, []rune{
|
||
|
0x7F,
|
||
|
}, []rune{
|
||
|
'␡',
|
||
|
}}, {
|
||
|
encoder.EncodeCtl,
|
||
|
runeRange(0x01, 0x1F),
|
||
|
runeRange('␁', '␟'),
|
||
|
}}
|
||
|
|
||
|
var (
|
||
|
rng = rand.New(rand.NewSource(42))
|
||
|
|
||
|
printables = runeRange(0x20, 0x7E)
|
||
|
fullwidthPrintables = runeRange(0xFF00, 0xFF5E)
|
||
|
encodables = collectEncodables(allMappings)
|
||
|
encoded = collectEncoded(allMappings)
|
||
|
greek = runeRange(0x03B1, 0x03C9)
|
||
|
)
|
||
|
|
||
|
func main() {
|
||
|
fd, err := os.Create("encoder_cases_test.go")
|
||
|
fatal(err, "Unable to open encoder_cases_test.go:")
|
||
|
defer func() {
|
||
|
fatal(fd.Close(), "Failed to close encoder_cases_test.go:")
|
||
|
}()
|
||
|
fatalW(fd.WriteString(header))("Failed to write header:")
|
||
|
|
||
|
fatalW(fd.WriteString("var testCasesSingle = []testCase{\n\t"))("Write:")
|
||
|
_i := 0
|
||
|
i := func() (r int) {
|
||
|
r, _i = _i, _i+1
|
||
|
return
|
||
|
}
|
||
|
for _, m := range maskBits {
|
||
|
if len(getMapping(m.mask).src) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
if _i != 0 {
|
||
|
fatalW(fd.WriteString(" "))("Write:")
|
||
|
}
|
||
|
in, out := buildTestString(
|
||
|
[]mapping{getMapping(m.mask)}, // pick
|
||
|
[]mapping{getMapping(0)}, // quote
|
||
|
printables, fullwidthPrintables, encodables, encoded, greek) // fill
|
||
|
fatalW(fmt.Fprintf(fd, `{ // %d
|
||
|
mask: %s,
|
||
|
in: %s,
|
||
|
out: %s,
|
||
|
},`, i(), m.name, strconv.Quote(in), strconv.Quote(out)))("Error writing test case:")
|
||
|
}
|
||
|
fatalW(fd.WriteString(`
|
||
|
}
|
||
|
|
||
|
var testCasesSingleEdge = []testCase{
|
||
|
`))("Write:")
|
||
|
_i = 0
|
||
|
for _, e := range edges {
|
||
|
if _i != 0 {
|
||
|
fatalW(fd.WriteString(" "))("Write:")
|
||
|
}
|
||
|
fatalW(fmt.Fprintf(fd, `{ // %d
|
||
|
mask: %s,
|
||
|
in: %s,
|
||
|
out: %s,
|
||
|
},`, i(), e.name, strconv.Quote(string(e.orig)), strconv.Quote(string(e.replace))))("Error writing test case:")
|
||
|
for _, m := range maskBits {
|
||
|
if len(getMapping(m.mask).src) == 0 {
|
||
|
continue
|
||
|
}
|
||
|
pairs := buildEdgeTestString(
|
||
|
e.edge, e.orig, e.replace,
|
||
|
[]mapping{getMapping(0), getMapping(m.mask)}, // quote
|
||
|
printables, fullwidthPrintables, encodables, encoded, greek) // fill
|
||
|
for _, p := range pairs {
|
||
|
fatalW(fmt.Fprintf(fd, ` { // %d
|
||
|
mask: %s | %s,
|
||
|
in: %s,
|
||
|
out: %s,
|
||
|
},`, i(), m.name, e.name, strconv.Quote(p.a), strconv.Quote(p.b)))("Error writing test case:")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
fatalW(fmt.Fprintf(fd, ` { // %d
|
||
|
mask: EncodeLeftSpace,
|
||
|
in: " ",
|
||
|
out: "␠ ",
|
||
|
}, { // %d
|
||
|
mask: EncodeLeftTilde,
|
||
|
in: "~~",
|
||
|
out: "~~",
|
||
|
}, { // %d
|
||
|
mask: EncodeRightSpace,
|
||
|
in: " ",
|
||
|
out: " ␠",
|
||
|
}, { // %d
|
||
|
mask: EncodeRightPeriod,
|
||
|
in: "..",
|
||
|
out: "..",
|
||
|
}, { // %d
|
||
|
mask: EncodeLeftSpace | EncodeRightPeriod,
|
||
|
in: " .",
|
||
|
out: "␠.",
|
||
|
}, { // %d
|
||
|
mask: EncodeLeftSpace | EncodeRightSpace,
|
||
|
in: " ",
|
||
|
out: "␠",
|
||
|
}, { // %d
|
||
|
mask: EncodeLeftSpace | EncodeRightSpace,
|
||
|
in: " ",
|
||
|
out: "␠␠",
|
||
|
}, { // %d
|
||
|
mask: EncodeLeftSpace | EncodeRightSpace,
|
||
|
in: " ",
|
||
|
out: "␠ ␠",
|
||
|
},
|
||
|
}
|
||
|
`, i(), i(), i(), i(), i(), i(), i(), i()))("Error writing test case:")
|
||
|
}
|
||
|
|
||
|
func fatal(err error, s ...interface{}) {
|
||
|
if err != nil {
|
||
|
log.Fatalln(append(s, err))
|
||
|
}
|
||
|
}
|
||
|
func fatalW(_ int, err error) func(...interface{}) {
|
||
|
if err != nil {
|
||
|
return func(s ...interface{}) {
|
||
|
log.Fatalln(append(s, err))
|
||
|
}
|
||
|
}
|
||
|
return func(s ...interface{}) {}
|
||
|
}
|
||
|
|
||
|
// construct a slice containing the runes between (l)ow (inclusive) and (h)igh (inclusive)
|
||
|
func runeRange(l, h rune) []rune {
|
||
|
if h < l {
|
||
|
panic("invalid range")
|
||
|
}
|
||
|
out := make([]rune, h-l+1)
|
||
|
for i := range out {
|
||
|
out[i] = l + rune(i)
|
||
|
}
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
func getMapping(mask uint) mapping {
|
||
|
for _, m := range allMappings {
|
||
|
if m.mask == mask {
|
||
|
return m
|
||
|
}
|
||
|
}
|
||
|
return mapping{}
|
||
|
}
|
||
|
func collectEncodables(m []mapping) (out []rune) {
|
||
|
for _, s := range m {
|
||
|
for _, r := range s.src {
|
||
|
out = append(out, r)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
func collectEncoded(m []mapping) (out []rune) {
|
||
|
for _, s := range m {
|
||
|
for _, r := range s.dst {
|
||
|
out = append(out, r)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func buildTestString(mappings, testMappings []mapping, fill ...[]rune) (string, string) {
|
||
|
combinedMappings := append(mappings, testMappings...)
|
||
|
var (
|
||
|
rIn []rune
|
||
|
rOut []rune
|
||
|
)
|
||
|
for _, m := range mappings {
|
||
|
if len(m.src) == 0 || len(m.src) != len(m.dst) {
|
||
|
panic("invalid length")
|
||
|
}
|
||
|
rIn = append(rIn, m.src...)
|
||
|
rOut = append(rOut, m.dst...)
|
||
|
}
|
||
|
inL := len(rIn)
|
||
|
testL := inL * 3
|
||
|
if testL < 30 {
|
||
|
testL = 30
|
||
|
}
|
||
|
rIn = append(rIn, make([]rune, testL-inL)...)
|
||
|
rOut = append(rOut, make([]rune, testL-inL)...)
|
||
|
quoteOut := make([]bool, testL)
|
||
|
set := func(i int, in, out rune, quote bool) {
|
||
|
rIn[i] = in
|
||
|
rOut[i] = out
|
||
|
quoteOut[i] = quote
|
||
|
}
|
||
|
for i, r := range rOut[:inL] {
|
||
|
set(inL+i, r, r, true)
|
||
|
}
|
||
|
|
||
|
outer:
|
||
|
for pos := inL * 2; pos < testL; pos++ {
|
||
|
m := pos % len(fill)
|
||
|
i := rng.Intn(len(fill[m]))
|
||
|
r := fill[m][i]
|
||
|
for _, m := range combinedMappings {
|
||
|
if pSrc := runePos(r, m.src); pSrc != -1 {
|
||
|
set(pos, r, m.dst[pSrc], false)
|
||
|
continue outer
|
||
|
} else if pDst := runePos(r, m.dst); pDst != -1 {
|
||
|
set(pos, r, r, true)
|
||
|
continue outer
|
||
|
}
|
||
|
}
|
||
|
set(pos, r, r, false)
|
||
|
}
|
||
|
|
||
|
rng.Shuffle(testL, func(i, j int) {
|
||
|
rIn[i], rIn[j] = rIn[j], rIn[i]
|
||
|
rOut[i], rOut[j] = rOut[j], rOut[i]
|
||
|
quoteOut[i], quoteOut[j] = quoteOut[j], quoteOut[i]
|
||
|
})
|
||
|
|
||
|
var bOut strings.Builder
|
||
|
bOut.Grow(testL)
|
||
|
for i, r := range rOut {
|
||
|
if quoteOut[i] {
|
||
|
bOut.WriteRune(encoder.QuoteRune)
|
||
|
}
|
||
|
bOut.WriteRune(r)
|
||
|
}
|
||
|
return string(rIn), bOut.String()
|
||
|
}
|
||
|
|
||
|
func buildEdgeTestString(edge int, orig, replace rune, testMappings []mapping, fill ...[]rune) (out []stringPair) {
|
||
|
testL := 30
|
||
|
rIn := make([]rune, testL)
|
||
|
rOut := make([]rune, testL)
|
||
|
quoteOut := make([]bool, testL)
|
||
|
|
||
|
set := func(i int, in, out rune, quote bool) {
|
||
|
rIn[i] = in
|
||
|
rOut[i] = out
|
||
|
quoteOut[i] = quote
|
||
|
}
|
||
|
|
||
|
outer:
|
||
|
for pos := 0; pos < testL; pos++ {
|
||
|
m := pos % len(fill)
|
||
|
i := rng.Intn(len(fill[m]))
|
||
|
r := fill[m][i]
|
||
|
for _, m := range testMappings {
|
||
|
if pSrc := runePos(r, m.src); pSrc != -1 {
|
||
|
set(pos, r, m.dst[pSrc], false)
|
||
|
continue outer
|
||
|
} else if pDst := runePos(r, m.dst); pDst != -1 {
|
||
|
set(pos, r, r, true)
|
||
|
continue outer
|
||
|
}
|
||
|
}
|
||
|
set(pos, r, r, false)
|
||
|
}
|
||
|
|
||
|
rng.Shuffle(testL, func(i, j int) {
|
||
|
rIn[i], rIn[j] = rIn[j], rIn[i]
|
||
|
rOut[i], rOut[j] = rOut[j], rOut[i]
|
||
|
quoteOut[i], quoteOut[j] = quoteOut[j], quoteOut[i]
|
||
|
})
|
||
|
set(10, orig, orig, false)
|
||
|
|
||
|
out = append(out, stringPair{string(rIn), quotedToString(rOut, quoteOut)})
|
||
|
for _, i := range []int{0, 1, testL - 2, testL - 1} {
|
||
|
for _, j := range []int{1, testL - 2, testL - 1} {
|
||
|
if j < i {
|
||
|
continue
|
||
|
}
|
||
|
rIn := append([]rune{}, rIn...)
|
||
|
rOut := append([]rune{}, rOut...)
|
||
|
quoteOut := append([]bool{}, quoteOut...)
|
||
|
|
||
|
for _, in := range []rune{orig, replace} {
|
||
|
expect, quote := in, false
|
||
|
if i == 0 && edge == edgeLeft ||
|
||
|
i == testL-1 && edge == edgeRight {
|
||
|
expect, quote = replace, in == replace
|
||
|
}
|
||
|
rIn[i], rOut[i], quoteOut[i] = in, expect, quote
|
||
|
|
||
|
if i != j {
|
||
|
for _, in := range []rune{orig, replace} {
|
||
|
expect, quote = in, false
|
||
|
if j == testL-1 && edge == edgeRight {
|
||
|
expect, quote = replace, in == replace
|
||
|
}
|
||
|
rIn[j], rOut[j], quoteOut[j] = in, expect, quote
|
||
|
}
|
||
|
}
|
||
|
out = append(out, stringPair{string(rIn), quotedToString(rOut, quoteOut)})
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func runePos(r rune, s []rune) int {
|
||
|
for i, c := range s {
|
||
|
if c == r {
|
||
|
return i
|
||
|
}
|
||
|
}
|
||
|
return -1
|
||
|
}
|
||
|
|
||
|
// quotedToString returns a string for the chars slice where a encoder.QuoteRune is
|
||
|
// inserted before a char[i] when quoted[i] is true.
|
||
|
func quotedToString(chars []rune, quoted []bool) string {
|
||
|
var out strings.Builder
|
||
|
out.Grow(len(chars))
|
||
|
for i, r := range chars {
|
||
|
if quoted[i] {
|
||
|
out.WriteRune(encoder.QuoteRune)
|
||
|
}
|
||
|
out.WriteRune(r)
|
||
|
}
|
||
|
return out.String()
|
||
|
}
|