xref: /redis-3.2.3/tests/integration/aof.tcl (revision 55003f7a)
1set defaults { appendonly {yes} appendfilename {appendonly.aof} }
2set server_path [tmpdir server.aof]
3set aof_path "$server_path/appendonly.aof"
4
5proc append_to_aof {str} {
6    upvar fp fp
7    puts -nonewline $fp $str
8}
9
10proc create_aof {code} {
11    upvar fp fp aof_path aof_path
12    set fp [open $aof_path w+]
13    uplevel 1 $code
14    close $fp
15}
16
17proc start_server_aof {overrides code} {
18    upvar defaults defaults srv srv server_path server_path
19    set config [concat $defaults $overrides]
20    set srv [start_server [list overrides $config]]
21    uplevel 1 $code
22    kill_server $srv
23}
24
25tags {"aof"} {
26    ## Server can start when aof-load-truncated is set to yes and AOF
27    ## is truncated, with an incomplete MULTI block.
28    create_aof {
29        append_to_aof [formatCommand set foo hello]
30        append_to_aof [formatCommand multi]
31        append_to_aof [formatCommand set bar world]
32    }
33
34    start_server_aof [list dir $server_path aof-load-truncated yes] {
35        test "Unfinished MULTI: Server should start if load-truncated is yes" {
36            assert_equal 1 [is_alive $srv]
37        }
38    }
39
40    ## Should also start with truncated AOF without incomplete MULTI block.
41    create_aof {
42        append_to_aof [formatCommand incr foo]
43        append_to_aof [formatCommand incr foo]
44        append_to_aof [formatCommand incr foo]
45        append_to_aof [formatCommand incr foo]
46        append_to_aof [formatCommand incr foo]
47        append_to_aof [string range [formatCommand incr foo] 0 end-1]
48    }
49
50    start_server_aof [list dir $server_path aof-load-truncated yes] {
51        test "Short read: Server should start if load-truncated is yes" {
52            assert_equal 1 [is_alive $srv]
53        }
54
55        set client [redis [dict get $srv host] [dict get $srv port]]
56
57        test "Truncated AOF loaded: we expect foo to be equal to 5" {
58            assert {[$client get foo] eq "5"}
59        }
60
61        test "Append a new command after loading an incomplete AOF" {
62            $client incr foo
63        }
64    }
65
66    # Now the AOF file is expected to be correct
67    start_server_aof [list dir $server_path aof-load-truncated yes] {
68        test "Short read + command: Server should start" {
69            assert_equal 1 [is_alive $srv]
70        }
71
72        set client [redis [dict get $srv host] [dict get $srv port]]
73
74        test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
75            assert {[$client get foo] eq "6"}
76        }
77    }
78
79    ## Test that the server exits when the AOF contains a format error
80    create_aof {
81        append_to_aof [formatCommand set foo hello]
82        append_to_aof "!!!"
83        append_to_aof [formatCommand set foo hello]
84    }
85
86    start_server_aof [list dir $server_path aof-load-truncated yes] {
87        test "Bad format: Server should have logged an error" {
88            set pattern "*Bad file format reading the append only file*"
89            set retry 10
90            while {$retry} {
91                set result [exec tail -n1 < [dict get $srv stdout]]
92                if {[string match $pattern $result]} {
93                    break
94                }
95                incr retry -1
96                after 1000
97            }
98            if {$retry == 0} {
99                error "assertion:expected error not found on config file"
100            }
101        }
102    }
103
104    ## Test the server doesn't start when the AOF contains an unfinished MULTI
105    create_aof {
106        append_to_aof [formatCommand set foo hello]
107        append_to_aof [formatCommand multi]
108        append_to_aof [formatCommand set bar world]
109    }
110
111    start_server_aof [list dir $server_path aof-load-truncated no] {
112        test "Unfinished MULTI: Server should have logged an error" {
113            set pattern "*Unexpected end of file reading the append only file*"
114            set retry 10
115            while {$retry} {
116                set result [exec tail -n1 < [dict get $srv stdout]]
117                if {[string match $pattern $result]} {
118                    break
119                }
120                incr retry -1
121                after 1000
122            }
123            if {$retry == 0} {
124                error "assertion:expected error not found on config file"
125            }
126        }
127    }
128
129    ## Test that the server exits when the AOF contains a short read
130    create_aof {
131        append_to_aof [formatCommand set foo hello]
132        append_to_aof [string range [formatCommand set bar world] 0 end-1]
133    }
134
135    start_server_aof [list dir $server_path aof-load-truncated no] {
136        test "Short read: Server should have logged an error" {
137            set pattern "*Unexpected end of file reading the append only file*"
138            set retry 10
139            while {$retry} {
140                set result [exec tail -n1 < [dict get $srv stdout]]
141                if {[string match $pattern $result]} {
142                    break
143                }
144                incr retry -1
145                after 1000
146            }
147            if {$retry == 0} {
148                error "assertion:expected error not found on config file"
149            }
150        }
151    }
152
153    ## Test that redis-check-aof indeed sees this AOF is not valid
154    test "Short read: Utility should confirm the AOF is not valid" {
155        catch {
156            exec src/redis-check-aof $aof_path
157        } result
158        assert_match "*not valid*" $result
159    }
160
161    test "Short read: Utility should be able to fix the AOF" {
162        set result [exec src/redis-check-aof --fix $aof_path << "y\n"]
163        assert_match "*Successfully truncated AOF*" $result
164    }
165
166    ## Test that the server can be started using the truncated AOF
167    start_server_aof [list dir $server_path aof-load-truncated no] {
168        test "Fixed AOF: Server should have been started" {
169            assert_equal 1 [is_alive $srv]
170        }
171
172        test "Fixed AOF: Keyspace should contain values that were parseable" {
173            set client [redis [dict get $srv host] [dict get $srv port]]
174            wait_for_condition 50 100 {
175                [catch {$client ping} e] == 0
176            } else {
177                fail "Loading DB is taking too much time."
178            }
179            assert_equal "hello" [$client get foo]
180            assert_equal "" [$client get bar]
181        }
182    }
183
184    ## Test that SPOP (that modifies the client's argc/argv) is correctly free'd
185    create_aof {
186        append_to_aof [formatCommand sadd set foo]
187        append_to_aof [formatCommand sadd set bar]
188        append_to_aof [formatCommand spop set]
189    }
190
191    start_server_aof [list dir $server_path aof-load-truncated no] {
192        test "AOF+SPOP: Server should have been started" {
193            assert_equal 1 [is_alive $srv]
194        }
195
196        test "AOF+SPOP: Set should have 1 member" {
197            set client [redis [dict get $srv host] [dict get $srv port]]
198            wait_for_condition 50 100 {
199                [catch {$client ping} e] == 0
200            } else {
201                fail "Loading DB is taking too much time."
202            }
203            assert_equal 1 [$client scard set]
204        }
205    }
206
207    ## Uses the alsoPropagate() API.
208    create_aof {
209        append_to_aof [formatCommand sadd set foo]
210        append_to_aof [formatCommand sadd set bar]
211        append_to_aof [formatCommand sadd set gah]
212        append_to_aof [formatCommand spop set 2]
213    }
214
215    start_server_aof [list dir $server_path] {
216        test "AOF+SPOP: Server should have been started" {
217            assert_equal 1 [is_alive $srv]
218        }
219
220        test "AOF+SPOP: Set should have 1 member" {
221            set client [redis [dict get $srv host] [dict get $srv port]]
222            wait_for_condition 50 100 {
223                [catch {$client ping} e] == 0
224            } else {
225                fail "Loading DB is taking too much time."
226            }
227            assert_equal 1 [$client scard set]
228        }
229    }
230
231    ## Test that EXPIREAT is loaded correctly
232    create_aof {
233        append_to_aof [formatCommand rpush list foo]
234        append_to_aof [formatCommand expireat list 1000]
235        append_to_aof [formatCommand rpush list bar]
236    }
237
238    start_server_aof [list dir $server_path aof-load-truncated no] {
239        test "AOF+EXPIRE: Server should have been started" {
240            assert_equal 1 [is_alive $srv]
241        }
242
243        test "AOF+EXPIRE: List should be empty" {
244            set client [redis [dict get $srv host] [dict get $srv port]]
245            wait_for_condition 50 100 {
246                [catch {$client ping} e] == 0
247            } else {
248                fail "Loading DB is taking too much time."
249            }
250            assert_equal 0 [$client llen list]
251        }
252    }
253
254    start_server {overrides {appendonly {yes} appendfilename {appendonly.aof}}} {
255        test {Redis should not try to convert DEL into EXPIREAT for EXPIRE -1} {
256            r set x 10
257            r expire x -1
258        }
259    }
260}
261