Test yang Hijau Siang, Merah Tiap Subuh
Test filter tanggal gagal tiap subuh jam 0-7 karena ada dua jam dalam satu test. Solusinya: ambil tanggal dari jam database, patok tengah hari.
Ringkasan
Testnya kadang ijo kadang merah tiap subuh gara-gara Go pake jam WIB sedangkan database pake UTC. Selisih tujuh jam bikin hitungan kemarin jadi berantakan dan filternya nangkep data yang salah. Akhirnya dibenerin dengan ngambil tanggal langsung dari MySQL dan naruh data seed di tengah hari biar aman.
Pola kegagalannya terlalu rapi buat kebetulan: test filter tanggal ini hijau kalau jalan siang sampai malam, terus merah lagi tiap subuh, kira-kira dari jam nol sampai jam tujuh. Kodenya nggak ada yang nyentuh, dependensi nggak pindah, tapi tiap pagi assertion tanggal kemarin selalu meleset satu hari. Dua-duanya deterministik malah, cuma jendela waktunya yang sempit. Test yang gagal acak itu nyebelin; yang gagal sesuai jadwal begini bikin merasa ada jam lain yang sedang berjalan. Ternyata memang begitu: ada dua jam di test itu, dan saya yang mempersilakan.
Dua Jam dalam Satu Test
Jam pertama dari Go. Ekspektasi tanggal kemarin di test saya ambil dari time.Now(), dan jam yang dipakai time itu nggak pernah netral: Local mengikuti zona sistem, di Unix baca variabel TZ, dan kalau kosong jatuh ke /etc/localtime [7]. Proses test jalan di host WIB, jadi "kemarin" versi Go pindah ke tanggal baru pas tengah malam WIB.
Jam kedua dari MySQL. Baris yang di-seed untuk test dipatok pakai NOW() di SQL, dan nilai fungsi semacam itu sensitif zona: mengikuti zona sesi server, yang default-nya ikut zona sistem server [6]. Database test itu jalan di container UTC. Jadi "sekarang" versi database tertinggal tujuh jam dari "sekarang" versi Go.
Keduanya benar secara individual, dan persis di situ keremukannya. Dari jam lima sore WIB, UTC sudah pindah hari duluan, dan sampai tengah malam WIB tanggal container tetap lebih maju dari tanggal host. Selama jendela itu, "kemarin" hitungan Go masih memuat "hari ini" hitungan database. Filter harinya menangkap baris yang menurut test nggak boleh ada, assertion runtuh, dan jam tujuh pagi semuanya normal lagi tanpa saya ubah apa-apa.
Jam yang Diuji, Satu-satunya Saksi
Perbaikannya bukan nerawat satu jam biar cocok dengan yang lain, tapi memilih satu saksi: jam dari pihak yang diuji. Filter tanggal itu dievaluasi MySQL, jadi tanggal ekspektasi pun harus lahir dari MySQL, bukan dari proses Go:
SELECT DATE_FORMAT(CURDATE(), '%Y-%m-%d');
UPDATE registration_submissions
SET created_at = DATE_SUB(?, INTERVAL 12 HOUR) WHERE id = ?;
CURDATE() mengambil tanggal hari ini [5], dan hasil DATE_FORMAT sengaja berupa string, supaya aman di konfigurasi koneksi apa pun, terutama soal parseTime. Dari satu tanggal itu, kemarin dihitung di sisi Go sebagai string, bukan dari jam proses lagi.
Bagian kedua justru lebih penting: baris seed dipatok ke tengah hari versi tanggal database lewat parameter, DATE_SUB(?, INTERVAL 12 HOUR) untuk kemarin dan DATE_ADD(?, INTERVAL 12 HOUR) untuk hari ini [5]. Jauh dari tengah malam zona mana pun, selisih tujuh jam antar jam nggak bisa merenggut baris ke tanggal yang salah lagi.
Aturan batasnya sendiri sudah benar dari sebelumnya: batas atas filter memakai to yang dieksekusi sebagai kurang-dari besok, sehingga hari ini tetap ikut terhitung. Yang bohong duluan bukan filternya, tapi bahan pembandingnya.
Yang Saya Catat sebagai Aturan
Insiden kecil ini lalu saya tulis jadi aturan tetap di rencana implementasi proyek: test yang menyentuh tanggal wajib menurunkan ekspektasinya dari jam database, bukan dari time.Now() proses. Soalnya, time.Now() itu jam dinding proses, campuran pembacaan waktu dinding dan monotonic [1], dan jam dinding dipengaruhi zona tempat proses hidup. Container boleh UTC, host boleh WIB, CI boleh lain lagi; test yang nggak memilih saksinya akan mewarisi semua perbedaan itu.
Punya dua jam dalam satu test itu bukan redundansi, itu konflik yang nunggu jadwal. Tentukan siapa saksi tunggal, patok semua penanda waktu ke dia, dan menjauhkan dari batas tanggal. Setelah itu test bisa jalan jam dua pagi sekalipun tanpa drama. Memang baru saya buktikan sampai jam segitu, tapi setidaknya sekarang saya nggak takut.