Rake task và CircleCi

Background

  • Dự án mình đang làm có một yêu cầu như sau: Viết một rake task thực hiện một chức năng xyz, nếu có lỗi validation thì hiển thị ra. Có khả năng thêm option --force vào câu lệnh để thực hiện chức năng ngay cả khi có lỗi validation.
  • Viết xong, chạy task xyz đó và rspecs (phải viết cả unit test cho rake task) trên máy local ổn, nhưng khi commit lên git, CircleCi báo lỗi ở một vài cases. Rõ ràng là ở máy local không sao???
  • Đây chỉ là một kinh nghiệm nho nhỏ mà đáng ra mình phải biết và làm từ đầu để tránh lỗi trên CircleCi.

Research

CircleCi báo lỗi thì đương nhiên phải bắt đầu điều tra nó trước.

Xem báo cáo của CircleCi, chỉ thấy báo lỗi ở một vài cases như đã nói ở trên, nhưng không hiểu vì sao lỗi, mình đã cố thiết lập sao cho giống với môi trường trên CircleCi, kể cả set RAILS_ENV=test trên máy local, nhưng ko kết quả gì.

Xem thêm terminal log của CircleCi thì nó chạy rake như sau:

bundle exec rspec --color --require spec_helper --format RspecJunitFormatter --out /tmp/circle-junit.WOzJ9A5/rspec/rspec.xml 'spec/file1.rb' 'spec/file2.rb' ...

Chạy như này trên máy local thì đúng báo lỗi thật. Trong các cases của rspecs thì đang chạy rake với chế độ silent, tức là không in gì ra log cả, tắt silent đi thì nhận được:

invalid option: --color
invalid option: --require
...

Vậy có nghĩa là những arguments trong câu lệnh chạy rspec thì nó cũng pass luôn vào rake. Có một vài cách để pass arguments vào rake, cách hiện tại đang dùng là:

task :task_name, [:arg_name_1] => :environment do |t, args|
  # lets dance!
end

Lúc đó cách chạy task sẽ là:

rake task_name[arg_name_1,arg2,arg3,...,argn]

Muốn sử dụng option kiểu --force vào thì phải dùng Ruby OptionParser để bắt nó:

require 'optparse'

option = OptionParser.new
option.banner = "Usage: rake task_name[options] -- -f[--force]"
option.on("-f", "--force") { |force| options[:force] = force }

Đây chính là điểm gây ra vấn đề, nếu không có cụm này thì thêm hàng đống option vào rake vẫn chạy bình thường. Một khi đã dùng OptionParser, phải quan tâm đến tất cả những option nào được/không được phép truyền vào. Nếu option truyền vào không có trong định nghĩa (option.on() ở trên), nó sẽ báo lỗi invalid option và dừng ngay tức khắc, chức năng không thực hiện được dẫn tới expect không đúng như trong rspecs đã viết, dẫn tới failures trên CircleCi.

Okay, vậy ở đây ta sẽ chỉ cho phép --force và loại bỏ tất cả những options khác.

other_options = []
begin
  option = OptionParser.new
  option.banner = "Usage: rake task_name[options] -- -f[--force]"
  option.on("-f", "--force") { |force| options[:force] = force }
rescue OptionParser::InvalidOption => e
   other_options.concat e.args
end

Hãy bắt ngoại lệ OptionParser::InvalidOption thì không một option thừa nào lọt được vào nữa, other_options chỉ là array để lưu lại những option đó nếu bạn muốn in tất cả chúng ra báo cho người dùng.

Vậy là xong!

Conclusion

  • Không phải cứ chạy test trước trên máy thì sẽ yên tâm là ngon lành trên CircleCi, hoặc bất cứ framework nào khác.
  • Nên định nghĩa những arguments được phép dùng trong task và bắt tất cả những gì không hợp lệ, nếu không task sẽ dừng. Mình chưa có kinh nghiệm viết rake task nhiều, nên giờ mới ngộ ra điểm này.
  • Thank God It's Friday, quẩy đê!