From 4fa9f2debc72b0cb93cb2de9e38601c1cdee1c0f Mon Sep 17 00:00:00 2001 From: "Y.Kentaro" Date: Wed, 17 Jun 2026 11:47:32 +0900 Subject: [PATCH] Migrate CI from Travis to GitHub Actions and add MySQL integration tests - Replace .travis.yml with .github/workflows/ci.yml - Test Ruby 3.2, 3.3, 3.4 and 4.0 on Ubuntu (4.0 allowed to fail) - Run the suite against a MySQL 8.4 service container - Add real-connection integration tests for mysql_bulk output - Update CI badge in README from Travis to GitHub Actions Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 57 ++++++++ .travis.yml | 14 -- README.md | 2 +- .../plugin/test_out_mysql_bulk_integration.rb | 133 ++++++++++++++++++ 4 files changed, 191 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml create mode 100644 test/plugin/test_out_mysql_bulk_integration.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0d49a2f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: Testing on Ubuntu +on: + push: + branches: [master] + pull_request: + branches: [master] + # Run monthly even without code changes: this plugin pins its dependencies + # loosely (e.g. fluentd, mysql2-cs-bind) and is maintained infrequently, so a + # scheduled build catches breakage caused by newer dependency or runner/MySQL + # image releases before users hit it. + schedule: + - cron: '0 0 1 * *' +jobs: + build: + runs-on: ${{ matrix.os }} + # Allow the newest Ruby (4.0) to fail without blocking CI. + continue-on-error: ${{ matrix.ruby == '4.0' }} + strategy: + fail-fast: false + matrix: + ruby: ['3.2', '3.3', '3.4', '4.0'] + os: + - ubuntu-latest + name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }} + services: + mysql: + image: mysql:8.4 + ports: + - 3306:3306 + env: + MYSQL_ROOT_PASSWORD: hogehoge + MYSQL_DATABASE: test_app_development + options: >- + --health-cmd="mysqladmin ping -h localhost" + --health-interval=10s + --health-timeout=5s + --health-retries=5 + steps: + - uses: actions/checkout@v4 + - name: Install MySQL client development headers + run: | + sudo apt-get update + sudo apt-get install -y default-libmysqlclient-dev + - uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + - name: unit testing + env: + CI: true + MYSQL_HOST: 127.0.0.1 + MYSQL_PORT: 3306 + MYSQL_USER: root + MYSQL_PASSWORD: hogehoge + MYSQL_DATABASE: test_app_development + run: | + bundle install --jobs 4 --retry 3 + bundle exec rake test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9b25faa..0000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: ruby - -rvm: - - 2.1 - - 2.2.3 - - 2.3.0 - -gemfile: - - Gemfile - -before_install: - - gem update bundler - -script: 'bundle exec rake test' diff --git a/README.md b/README.md index b032b83..d8c29d3 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# fluent-plugin-mysql, a plugin for [Fluentd](http://fluentd.org) [![Build Status](https://secure.travis-ci.org/tagomoris/fluent-plugin-mysql.png?branch=master)](http://travis-ci.org/tagomoris/fluent-plugin-mysql) +# fluent-plugin-mysql, a plugin for [Fluentd](http://fluentd.org) [![Testing on Ubuntu](https://github.com/tagomoris/fluent-plugin-mysql/actions/workflows/ci.yml/badge.svg)](https://github.com/tagomoris/fluent-plugin-mysql/actions/workflows/ci.yml) fluent plugin mysql bulk insert is high performance and on duplicate key update respond. diff --git a/test/plugin/test_out_mysql_bulk_integration.rb b/test/plugin/test_out_mysql_bulk_integration.rb new file mode 100644 index 0000000..d078e8c --- /dev/null +++ b/test/plugin/test_out_mysql_bulk_integration.rb @@ -0,0 +1,133 @@ +# coding: utf-8 +require 'helper' +require 'mysql2-cs-bind' +require 'fluent/test/driver/output' +require 'fluent/test/helpers' +require 'fluent/config' +require 'time' + +# Integration tests that exercise the real INSERT path against a live MySQL +# server (MySQL 8.4 is used on CI). When no MySQL server is reachable the whole +# test case is omitted, so it is safe to run locally without a database. +class MysqlBulkOutputIntegrationTest < Test::Unit::TestCase + include Fluent::Test::Helpers + + HOST = ENV['MYSQL_HOST'] || '127.0.0.1' + PORT = (ENV['MYSQL_PORT'] || 3306).to_i + USERNAME = ENV['MYSQL_USER'] || 'root' + PASSWORD = ENV['MYSQL_PASSWORD'] || 'hogehoge' + DATABASE = ENV['MYSQL_DATABASE'] || 'test_app_development' + TABLE = 'users' + + def maybe_client(database: nil) + Mysql2::Client.new(host: HOST, port: PORT, username: USERNAME, + password: PASSWORD, database: database) + rescue => e + @connection_error = e + nil + end + + def setup + client = maybe_client + omit "MySQL is not available (#{@connection_error}); skipping integration tests" if client.nil? + + Fluent::Test.setup + + client.query("CREATE DATABASE IF NOT EXISTS `#{DATABASE}`") + client.close + + @client = maybe_client(database: DATABASE) + @client.query("DROP TABLE IF EXISTS `#{TABLE}`") + @client.query(<<-SQL) + CREATE TABLE `#{TABLE}` ( + `id` INT NOT NULL, + `user_name` VARCHAR(50) DEFAULT NULL, + `created_at` DATETIME DEFAULT NULL, + `login_count` INT DEFAULT NULL, + PRIMARY KEY (`id`) + ) + SQL + end + + def teardown + return unless @client + @client.query("DROP TABLE IF EXISTS `#{TABLE}`") + @client.close + end + + def create_driver(conf) + Fluent::Test::Driver::Output.new(Fluent::Plugin::MysqlBulkOutput).configure(conf) + end + + def base_config(extra = '') + %[ + host #{HOST} + port #{PORT} + database #{DATABASE} + username #{USERNAME} + password #{PASSWORD} + table #{TABLE} + #{extra} + ] + end + + def select_all + @client.query("SELECT * FROM `#{TABLE}` ORDER BY id").to_a + end + + def test_bulk_insert + conf = base_config(%[ + column_names id,user_name,created_at + key_names id,user_name,created_at + ]) + d = create_driver(conf) + time = event_time("2016-09-26 12:00:00 UTC") + d.run(default_tag: 'test', flush: true) do + d.feed(time, { "id" => 1, "user_name" => "alice", "created_at" => "2016-09-26 12:00:00" }) + d.feed(time, { "id" => 2, "user_name" => "bob", "created_at" => "2016-09-26 12:00:00" }) + end + + rows = select_all + assert_equal(2, rows.size) + assert_equal([1, "alice"], [rows[0]["id"], rows[0]["user_name"]]) + assert_equal([2, "bob"], [rows[1]["id"], rows[1]["user_name"]]) + end + + def test_varchar_is_truncated_to_column_length + conf = base_config(%[ + column_names id,user_name + key_names id,user_name + ]) + d = create_driver(conf) + long_name = "a" * 100 # column is VARCHAR(50) + time = event_time("2016-09-26 12:00:00 UTC") + d.run(default_tag: 'test', flush: true) do + d.feed(time, { "id" => 1, "user_name" => long_name }) + end + + rows = select_all + assert_equal(1, rows.size) + assert_equal(50, rows[0]["user_name"].length) + end + + def test_on_duplicate_key_update_with_custom_value + @client.query("INSERT INTO `#{TABLE}` (id, user_name, login_count) VALUES (1, 'alice', 10)") + + conf = base_config(%[ + column_names id,user_name,login_count + key_names id,user_name,login_count + on_duplicate_key_update true + on_duplicate_update_keys login_count + on_duplicate_update_custom_values ${`login_count` + 1} + ]) + d = create_driver(conf) + time = event_time("2016-09-26 12:00:00 UTC") + d.run(default_tag: 'test', flush: true) do + d.feed(time, { "id" => 1, "user_name" => "alice", "login_count" => 10 }) + end + + rows = select_all + assert_equal(1, rows.size) + assert_equal(11, rows[0]["login_count"]) + end +end