Well, I’ve written the program to solve it, and all tests look like the following:
=== RUN TestAtbash/encode_yes
atbash_cipher_test.go:10: Atbash('yes'): expected 'bvh', actual 'bvh'
--- FAIL: TestAtbash/encode_yes (0.00s)
=== RUN TestAtbash/encode_no
atbash_cipher_test.go:10: Atbash('no'): expected 'ml', actual 'ml'
--- FAIL: TestAtbash/encode_no (0.00s)
=== RUN TestAtbash/encode_all_the_letters
atbash_cipher_test.go:10: Atbash('The quick brown fox jumps over the lazy dog.'): expected 'gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt', actual 'gsvjf rxpyi ldmul cqfnk hlevi gsvoz abwlt'
--- FAIL: TestAtbash/encode_all_the_letters (0.00s)
What the hell is going here? I have “expected” the same as “actual” in all cases, and yet every test fails. The code is:
package atbash
func Atbash(s string) string {
var ob = make([]byte, len(s)*6/5)
var group int
for _, b := range []byte(s) {
if (b>='a') && (b<='z') {
ob = append(ob, 25+'a'-b+'a')
} else if (b>='A') && (b<='Z') {
ob = append(ob, 25+'A'-b+'a')
} else if (b>='0') && (b<='9') {
ob = append(ob, b)
} else {
group -= 1
}
group += 1
if group == 5 {
group = 0
ob = append(ob, ' ')
}
}
if ob[len(ob)-1] == ' ' {
ob = ob[:len(ob)-1]
}
return string(ob)
}
actual := Atbash(tc.phrase)
if actual != tc.expected {
t.Errorf("Atbash('%s'): expected '%s', actual '%s'", tc.phrase, tc.expected, actual)
}
I suspected that the comparison in the test (!=) is probably “too deep” and inspects the underlying arrays (no matter how stupid is the idea to compare strings this way). But no way I was able to manipulate my string before returning it so it works. I even tried to implement this string(ob[:]) myself, the dumb way:
var q = make([]byte, len(ob))
for i, c := range ob {
q[i] = c
}
return string(q)
Also imported fmt:
return fmt.Sprintf("%v", string(ob))
None of that helped either. The test output is the same: visually the expected and actual are the same, yet tests fail.
I think it might be important to note I’m using web version of Exercism.
Well, even []byte{} works. make([]byte, len(s)) and make([]rune, len(s)) does not. And now I get why.
Yes, there is a whole lot of extra 0-bytes in the beginning. Totally invisible. I wanted to allocate array big enough so it doesn’t need to do reallocation, but accidentally set length of the slice to that size, instead of zero. What I was intended to write is make([]byte, 0, 6*len(s)/5); notice the extra 0 argument in the middle.