feature image

2026年3月11日 | ブログ記事

おもしろバグ: 3月1日だけテストが落ちる

この記事は新歓ブログリレー2026 6日目の記事です。

22B (4 月から 26M)の @ikura-hamu です。traP では主に Go でサーバーアプリケーションを書いています。この記事では、最近出会った面白いバグについて説明します。

バグの概要

SysAd 班では部内メッセージングサービス traQ をはじめとしていくつかの部内サービスを開発・運用していますが、僕はゲーム管理・配布アプリである traP Collection というサービスのサーバーを書いています。このバグは traP Collection のサーバーのプログラムで起きました。

GitHub - traPtitech/trap-collection-server: 内製ゲーム管理システム traP Collection サーバーアプリケーション
内製ゲーム管理システム traP Collection サーバーアプリケーション. Contribute to traPtitech/trap-collection-server development by creating an account on GitHub.

traP Collection では多くのユニットテストが記述されています。今年の 3 月 1 日に今までは通っていたテストが急に落ちるようになりました。テスト対象の関数とテストケースを抜き出すと以下のようになります。

テスト対象の関数

保存されているゲームのプレイ時間の統計情報を、期間を決めて取得する関数です。あまりに長い期間を指定するとデータベースに負荷がかかることが想定されるので、指定期間が 10 年より長かったらエラーになるようにしています。

func (g *GamePlayLog) GetEditionPlayStats(
	ctx context.Context, editionID values.EditionID, start, end time.Time,
) (*domain.EditionPlayStats, error) {
	if end.Before(start) {
		return nil, service.ErrInvalidTimeRange
	}

	const maxYears = 10
	if start.AddDate(maxYears, 0, 0).Before(end) {
		// start と end の間が10 年より長かったらエラー
		return nil, service.ErrTimePeriodTooLong
	}

	//...省略
	return stats, nil
}

テストケース

10 年を超えてる期間を指定した際に ErrTimePeriodTooLong を返すことを確かめるためのテストケースです。開始時刻を現在時刻から 10 年と 1 日引いた時刻、終了時刻を現在時刻として、10 年と 1 日の範囲を指定するようになっています。

{
	description:       "期間が10年を超えるのでErrTimePeriodTooLong",
	editionID:         editionID,
	start:             now.AddDate(-10, 0, -1),
	end:               now,
	executeGetEdition: false,
	isErr:             true,
	err:               service.ErrTimePeriodTooLong,
},

テストが落ちた内容

本来は ErrTimePeriodTooLong が返ってくるはずですが、そのエラーが返ってこずに処理が先に進んでしまったことによってテストが落ちていました。つまり、「10 年より長かったらエラー」の処理が正しく処理されていないということになります。

バグの原因

おそらく察しのいい方であれば原因に気づくことができたのではないのでしょうか。このバグは、この日が 3 月 1 日であったことと、10 年前の 2016 年がうるう年であったことが組み合わさって発生しています。

再現のためのプログラムと出力は以下のようになります。

package main

import (
	"fmt"
	"time"
)

func main() {
	now := time.Date(2026, 3, 1, 10, 0, 0, 0, time.UTC)
	fmt.Println("now:\t\t\t\t\t", now)
	start := now.AddDate(-10, 0, -1)  // 10年と1日引く
	fmt.Println("start=now.AddDate(-10, 0, -1):\t\t", start)
	fmt.Println("start.AddDate(10, 0, 0):\t\t", start.AddDate(10, 0, 0)) // 10年と1日引いたものに10年足す
	fmt.Println("start.AddDate(10, 0, 0).Before(now):\t", start.AddDate(10, 0, 0).Before(now))
}
now:					 2026-03-01 10:00:00 +0000 UTC
start=now.AddDate(-10, 0, -1):		 2016-02-29 10:00:00 +0000 UTC
start.AddDate(10, 0, 0):		 2026-03-01 10:00:00 +0000 UTC
start.AddDate(10, 0, 0).Before(now):	 false

https://go.dev/play/p/Oab5QCEL3XP

2026 年 3 月 1 日の 10 年と 1 日前は、2016 年はうるう年ですから、2016 年 2 月 29 日になります。これに 10 年を足すと、2026 年はうるう年ではないですから、2026 年 3 月 1 日に戻ってきてしまいます。うるう年によって生まれたこの差によって、本来は 10 年と 1 日あるはずの差が 10 年ちょうどになってしまい、テストが落ちてしまっていたのでした。

対策

夜遅かったので、次の日まで待ちました。

----------2026-03-05-231002

機能としては 1 日の差があっても特に問題ないので、いったん放置しています。

ただ、時間関係で他のテストが落ちるのはよくあるので、テスト中の現在時刻を使わず時刻を固定するなどの処置は将来的にしたいと思っています。

おわり

新入生のみなさんは、ぜひ traP に入っておもしろバグと一緒に楽しく生活しましょう。入部を楽しみに待っています。

明日の新歓ブログリレーの担当者は、現代表の @zoi_dayoです。

ikura-hamu icon
この記事を書いた人
ikura-hamu

SysAd班、ゲーム班 いろいろやりたい

この記事をシェア

このエントリーをはてなブックマークに追加
共有

関連する記事

2021年8月12日
CPCTFを支えたWebshell
mazrean icon mazrean
2021年5月19日
CPCTF2021を実現させたスコアサーバー
xxpoxx icon xxpoxx
2026年2月4日
2025年度 1-Monthon 12 班: 総合進捗環境『Progres』
uni_kakurenbo icon uni_kakurenbo
2026年2月2日
2025年度冬ハッカソン 16班 部内経済基盤『Plutus』
o_o icon o_o
2024年6月21日
ハッカソン参加記 4班"Slide Center"
Alt--er icon Alt--er
2024年3月15日
個人開発として2週間でWebサービスを作ってみた話 〜「LABEL」の紹介〜
Natsuki icon Natsuki
記事一覧 タグ一覧 Google アナリティクスについて 特定商取引法に基づく表記