Skip to content

Commit 9e752b8

Browse files
added some unit tests for checkpoint
1 parent a3a8f09 commit 9e752b8

2 files changed

Lines changed: 113 additions & 0 deletions

File tree

go/base/context_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,44 @@ func TestGetTableNames(t *testing.T) {
6161
}
6262
}
6363

64+
func TestMoveTableDelName(t *testing.T) {
65+
context := NewMigrationContext()
66+
// Per-table `_<table>_del` rollback handle, independent of any other table.
67+
require.Equal(t, "_some_table_del", context.MoveTableDelName("some_table"))
68+
require.Equal(t, "_other_del", context.MoveTableDelName("other"))
69+
70+
// Honors --timestamp-old-table like the single-table GetOldTableName does.
71+
context.TimestampOldTable = true
72+
longForm := "Jan 2, 2006 at 3:04pm (MST)"
73+
context.StartTime, _ = time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
74+
require.Equal(t, "_some_table_20130203195400_del", context.MoveTableDelName("some_table"))
75+
}
76+
77+
func TestMoveTablesRunToken(t *testing.T) {
78+
// Empty outside move-tables mode.
79+
require.Equal(t, "", NewMigrationContext().MoveTablesRunToken())
80+
81+
context := NewMigrationContext()
82+
context.MoveTables.TableNames = []string{"a", "b", "c"}
83+
token := context.MoveTablesRunToken()
84+
// Fixed length, lowercase hex (12 chars / 48 bits).
85+
require.Len(t, token, 12)
86+
require.Regexp(t, "^[0-9a-f]{12}$", token)
87+
// Deterministic: the same set always yields the same token (so a resumed run
88+
// finds the same run-wide artifacts).
89+
require.Equal(t, token, context.MoveTablesRunToken())
90+
91+
// Order-independent: --move-tables=a,b,c and =c,b,a match.
92+
reordered := NewMigrationContext()
93+
reordered.MoveTables.TableNames = []string{"c", "b", "a"}
94+
require.Equal(t, token, reordered.MoveTablesRunToken())
95+
96+
// A different set yields a different token.
97+
different := NewMigrationContext()
98+
different.MoveTables.TableNames = []string{"a", "b", "d"}
99+
require.NotEqual(t, token, different.MoveTablesRunToken())
100+
}
101+
64102
func TestGetTriggerNames(t *testing.T) {
65103
{
66104
context := NewMigrationContext()

go/logic/checkpoint_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
Copyright 2025 GitHub Inc.
3+
See https://github.com/github/gh-ost/blob/master/LICENSE
4+
*/
5+
6+
package logic
7+
8+
import (
9+
"testing"
10+
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/github/gh-ost/go/sql"
14+
)
15+
16+
// TestSerializeRangeValues covers the table-agnostic, hex-per-value encoding used
17+
// to store a move-table's unique-key range in the single checkpoint table.
18+
func TestSerializeRangeValues(t *testing.T) {
19+
// nil ColumnValues serializes to the empty string.
20+
require.Equal(t, "", serializeRangeValues(nil))
21+
22+
// A single integer key: hex of its decimal text ("172" -> 31 37 32).
23+
require.Equal(t, "313732", serializeRangeValues(sql.ToColumnValues([]interface{}{172})))
24+
25+
// A varchar key: hex of the UTF-8 bytes ("code_8" -> 63 6f 64 65 5f 38).
26+
require.Equal(t, "636f64655f38", serializeRangeValues(sql.ToColumnValues([]interface{}{"code_8"})))
27+
28+
// A compound key of heterogeneous types is comma-joined.
29+
require.Equal(t, "3235,636f64655f38",
30+
serializeRangeValues(sql.ToColumnValues([]interface{}{25, "code_8"})))
31+
32+
// Raw bytes are hex-encoded as-is.
33+
require.Equal(t, "e590",
34+
serializeRangeValues(sql.ToColumnValues([]interface{}{[]byte{0xe5, 0x90}})))
35+
36+
// A nil column value is encoded with the unambiguous NULL token.
37+
require.Equal(t, moveTableCheckpointNullToken,
38+
serializeRangeValues(sql.ToColumnValues([]interface{}{nil})))
39+
}
40+
41+
// TestDeserializeRangeValuesRoundTrip verifies the encode->store->decode cycle.
42+
// Values come back as []byte (accepted directly as prepared-statement args), so
43+
// the round trip is checked on the serialized (canonical) form, which is what a
44+
// resumed run actually compares.
45+
func TestDeserializeRangeValuesRoundTrip(t *testing.T) {
46+
orig := sql.ToColumnValues([]interface{}{172, "code_8"})
47+
s := serializeRangeValues(orig)
48+
49+
got := deserializeRangeValues(s, 2)
50+
require.Equal(t, s, serializeRangeValues(got), "re-serializing the decoded range must reproduce the stored text")
51+
52+
vals := got.AbstractValues()
53+
require.Len(t, vals, 2)
54+
require.Equal(t, []byte("172"), vals[0])
55+
require.Equal(t, []byte("code_8"), vals[1])
56+
}
57+
58+
// TestDeserializeRangeValuesNullToken verifies the NULL marker decodes back to a
59+
// nil column value while other columns decode normally.
60+
func TestDeserializeRangeValuesNullToken(t *testing.T) {
61+
got := deserializeRangeValues(moveTableCheckpointNullToken+",3235", 2)
62+
vals := got.AbstractValues()
63+
require.Len(t, vals, 2)
64+
require.Nil(t, vals[0])
65+
require.Equal(t, []byte("25"), vals[1])
66+
}
67+
68+
// TestIsEmptyRange verifies the predicate that tells a resumed run a table had no
69+
// completed chunk yet (so it must restart from the table minimum).
70+
func TestIsEmptyRange(t *testing.T) {
71+
require.True(t, isEmptyRange(nil), "nil range is empty")
72+
require.True(t, isEmptyRange(sql.NewColumnValues(0)), "zero-column range is empty")
73+
require.True(t, isEmptyRange(deserializeRangeValues(moveTableCheckpointNullToken, 1)), "all-nil range is empty")
74+
require.False(t, isEmptyRange(sql.ToColumnValues([]interface{}{1})), "a range with a value is not empty")
75+
}

0 commit comments

Comments
 (0)