mirror of
https://github.com/ditkrg/active_model_serializers.git
synced 2026-01-22 22:06:50 +00:00
Merge pull request #2207 from bf4/benchmarks
Add payload (regression) validation to benchmarks
This commit is contained in:
commit
b30b8cae73
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,3 +32,4 @@ tags
|
|||||||
Icon?
|
Icon?
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
*.sqlite3
|
||||||
|
|||||||
10
.travis.yml
Normal file
10
.travis.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
language: ruby
|
||||||
|
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- vendor/bundle
|
||||||
|
|
||||||
|
script:
|
||||||
|
- true
|
||||||
11
appveyor.yml
Normal file
11
appveyor.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
version: 1.0.{build}-{branch}
|
||||||
|
|
||||||
|
skip_tags: true
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- vendor/bundle
|
||||||
|
|
||||||
|
test_script:
|
||||||
|
- true
|
||||||
|
|
||||||
|
build: off
|
||||||
@ -3,6 +3,8 @@ Bundler.require(*Rails.groups)
|
|||||||
|
|
||||||
ActiveRecord::Base.logger = nil
|
ActiveRecord::Base.logger = nil
|
||||||
ActiveModelSerializers.logger = nil
|
ActiveModelSerializers.logger = nil
|
||||||
|
ActiveModelSerializers.config.adapter = :json_api
|
||||||
|
ActiveModelSerializers.config.key_transform = :unaltered
|
||||||
|
|
||||||
require './support/rails'
|
require './support/rails'
|
||||||
require './support/bench_helper'
|
require './support/bench_helper'
|
||||||
@ -22,6 +24,8 @@ GC.disable
|
|||||||
%i[ips memory].each do |bench|
|
%i[ips memory].each do |bench|
|
||||||
BenchHelper.clear_data
|
BenchHelper.clear_data
|
||||||
BenchHelper.seed_data
|
BenchHelper.seed_data
|
||||||
|
BenchHelper.validate_render(:ams)
|
||||||
|
BenchHelper.validate_render(:jsonapi_rb)
|
||||||
|
|
||||||
Benchmark.send(bench) do |x|
|
Benchmark.send(bench) do |x|
|
||||||
x.config(time: 10, warmup: 5, stats: :bootstrap, confidence: 95) if x.respond_to?(:config)
|
x.config(time: 10, warmup: 5, stats: :bootstrap, confidence: 95) if x.respond_to?(:config)
|
||||||
|
|||||||
222
benchmarks/serialization_libraries/compare.sh
Executable file
222
benchmarks/serialization_libraries/compare.sh
Executable file
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Looks for significant differences between jsonapi-rb vs. ams serialization.
|
||||||
|
#
|
||||||
|
# Strategy:
|
||||||
|
# 1. The data being serialized is the same every time.
|
||||||
|
# i.e. All ids and timestamps or otherwise variable fields being serialized
|
||||||
|
# are always the same.
|
||||||
|
# 2. Compare the data in a way that accounts for the order different resources are included.
|
||||||
|
# 1. Compare the .data members, sort keys.
|
||||||
|
# 2. Compare the .included posts, sort keys.
|
||||||
|
# 3. Compare the .included comments, sort keys.
|
||||||
|
# 4. Confirm the included members are all either posts or comments.
|
||||||
|
#
|
||||||
|
# Depends on:
|
||||||
|
# jq: installed via npm
|
||||||
|
# diff: installed on system
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./compare.sh
|
||||||
|
#
|
||||||
|
# Example output (abridged):
|
||||||
|
#
|
||||||
|
# -------------------------------------data (user)
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "birthday": "2017-07-01 05:00:00 UTC", "birthday": "2017-07-01 05:00:00 UTC",
|
||||||
|
# "created_at": "2017-07-01 05:00:00 UTC", "created_at": "2017-07-01 05:00:00 UTC",
|
||||||
|
# "first_name": "Diana", "first_name": "Diana",
|
||||||
|
# "last_name": "Prince", "last_name": "Prince",
|
||||||
|
# "updated_at": "2017-07-01 05:00:00 UTC" "updated_at": "2017-07-01 05:00:00 UTC"
|
||||||
|
# }, },
|
||||||
|
# "id": "1", "id": "1",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "posts": { "posts": {
|
||||||
|
# "data": [ "data": [
|
||||||
|
# { {
|
||||||
|
# "id": "2", "id": "2",
|
||||||
|
# "type": "posts" "type": "posts"
|
||||||
|
# }, },
|
||||||
|
# { {
|
||||||
|
# "id": "5", "id": "5",
|
||||||
|
# "type": "posts" "type": "posts"
|
||||||
|
# } }
|
||||||
|
# ] ]
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "users" "type": "users"
|
||||||
|
# } }
|
||||||
|
# -------------------------------------included posts
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "body": "awesome content", "body": "awesome content",
|
||||||
|
# "created_at": "2017-07-01 05:00:00 UTC", "created_at": "2017-07-01 05:00:00 UTC",
|
||||||
|
# "title": "Some Post", "title": "Some Post",
|
||||||
|
# "updated_at": "2017-07-01 05:00:00 UTC" "updated_at": "2017-07-01 05:00:00 UTC"
|
||||||
|
# }, },
|
||||||
|
# "id": "2", "id": "2",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "comments": { "comments": {
|
||||||
|
# "data": [ "data": [
|
||||||
|
# { {
|
||||||
|
# "id": "3", "id": "3",
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# }, },
|
||||||
|
# { {
|
||||||
|
# "id": "4", "id": "4",
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# ] ]
|
||||||
|
# }, },
|
||||||
|
# "user": { "user": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "1",
|
||||||
|
# > "type": "users"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "posts" "type": "posts"
|
||||||
|
# } }
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "body": "awesome content", "body": "awesome content",
|
||||||
|
# "created_at": "2017-07-01 05:00:00 UTC", "created_at": "2017-07-01 05:00:00 UTC",
|
||||||
|
# "title": "Some Post", "title": "Some Post",
|
||||||
|
# "updated_at": "2017-07-01 05:00:00 UTC" "updated_at": "2017-07-01 05:00:00 UTC"
|
||||||
|
# }, },
|
||||||
|
# "id": "5", "id": "5",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "comments": { "comments": {
|
||||||
|
# "data": [ "data": [
|
||||||
|
# { {
|
||||||
|
# "id": "6", "id": "6",
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# }, },
|
||||||
|
# { {
|
||||||
|
# "id": "7", "id": "7",
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# ] ]
|
||||||
|
# }, },
|
||||||
|
# "user": { "user": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "1",
|
||||||
|
# > "type": "users"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "posts" "type": "posts"
|
||||||
|
# } }
|
||||||
|
# -------------------------------------included comments
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "author": "me", "author": "me",
|
||||||
|
# "comment": "nice blog" "comment": "nice blog"
|
||||||
|
# }, },
|
||||||
|
# "id": "3", "id": "3",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "post": { "post": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "2",
|
||||||
|
# > "type": "posts"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "author": "me", "author": "me",
|
||||||
|
# "comment": "nice blog" "comment": "nice blog"
|
||||||
|
# }, },
|
||||||
|
# "id": "4", "id": "4",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "post": { "post": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "2",
|
||||||
|
# > "type": "posts"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "author": "me", "author": "me",
|
||||||
|
# "comment": "nice blog" "comment": "nice blog"
|
||||||
|
# }, },
|
||||||
|
# "id": "6", "id": "6",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "post": { "post": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "5",
|
||||||
|
# > "type": "posts"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# { {
|
||||||
|
# "attributes": { "attributes": {
|
||||||
|
# "author": "me", "author": "me",
|
||||||
|
# "comment": "nice blog" "comment": "nice blog"
|
||||||
|
# }, },
|
||||||
|
# "id": "7", "id": "7",
|
||||||
|
# "relationships": { "relationships": {
|
||||||
|
# "post": { "post": {
|
||||||
|
# "meta": { | "data": {
|
||||||
|
# "included": false | "id": "5",
|
||||||
|
# > "type": "posts"
|
||||||
|
# } }
|
||||||
|
# } }
|
||||||
|
# }, },
|
||||||
|
# "type": "comments" "type": "comments"
|
||||||
|
# } }
|
||||||
|
# -------------------------------------included types: ams
|
||||||
|
# "comments"
|
||||||
|
# "posts"
|
||||||
|
# -------------------------------------included types: jsonapi_rb
|
||||||
|
# "comments"
|
||||||
|
# "posts"
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
DEBUG="${DEBUG:-false}"
|
||||||
|
|
||||||
|
if ! command -v jq &>/dev/null; then
|
||||||
|
npm install -g jq
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_for_file() {
|
||||||
|
local path
|
||||||
|
path="$1"
|
||||||
|
result="$(cat ${path})"
|
||||||
|
echo "$result"
|
||||||
|
}
|
||||||
|
|
||||||
|
compare_files() {
|
||||||
|
local actual
|
||||||
|
local expected
|
||||||
|
local pattern
|
||||||
|
actual="$1"
|
||||||
|
expected="$2"
|
||||||
|
pattern="$3"
|
||||||
|
|
||||||
|
if [ "$DEBUG" = "true" ]; then get_for_file "${expected}" >&2; fi
|
||||||
|
diff --side-by-side \
|
||||||
|
<(jq "${pattern}" -S <(get_for_file "${expected}")) \
|
||||||
|
<(jq "${pattern}" -S <(get_for_file "${actual}"))
|
||||||
|
}
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo "-------------------------------------data (user)"
|
||||||
|
compare_files support/json_document-ams.json support/json_document-jsonapi_rb.json ".data"
|
||||||
|
echo "-------------------------------------included posts"
|
||||||
|
compare_files support/json_document-ams.json support/json_document-jsonapi_rb.json ".included | .[] | select(.type == \"posts\")"
|
||||||
|
echo "-------------------------------------included comments"
|
||||||
|
compare_files support/json_document-ams.json support/json_document-jsonapi_rb.json ".included | .[] | select(.type == \"comments\")"
|
||||||
|
echo "-------------------------------------included types: ams"
|
||||||
|
jq '.included | .[] | .type' -S < support/json_document-ams.json | sort -u
|
||||||
|
echo "-------------------------------------included types: jsonapi_rb"
|
||||||
|
jq '.included | .[] | .type' -S < support/json_document-jsonapi_rb.json | sort -u
|
||||||
Binary file not shown.
@ -13,16 +13,54 @@ module BenchHelper
|
|||||||
posts: 20
|
posts: 20
|
||||||
}
|
}
|
||||||
|
|
||||||
u = User.create(first_name: 'Diana', last_name: 'Prince', birthday: 3000.years.ago)
|
anchor_time = Time.new(2017,7,1).utc
|
||||||
|
id = 1
|
||||||
|
user = User.create!(id: id, first_name: 'Diana', last_name: 'Prince', birthday: anchor_time, created_at: anchor_time, updated_at: anchor_time)
|
||||||
|
id += 1
|
||||||
|
|
||||||
data_config[:posts].times do
|
data_config[:posts].times do
|
||||||
p = Post.create(user_id: u.id, title: 'Some Post', body: 'awesome content')
|
post = Post.create!(id: id, user_id: user.id, title: 'Some Post', body: 'awesome content', created_at: anchor_time, updated_at: anchor_time)
|
||||||
|
id += 1
|
||||||
data_config[:comments_per_post].times do
|
data_config[:comments_per_post].times do
|
||||||
Comment.create(author: 'me', comment: 'nice blog', post_id: p.id)
|
Comment.create!(id: id, author: 'me', comment: 'nice blog', post_id: post.id, created_at: anchor_time, updated_at: anchor_time)
|
||||||
|
id += 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_render(render_gem)
|
||||||
|
expected_json_file = File.join("support", "json_document-#{render_gem}.json")
|
||||||
|
json_document = test_render(render_gem)
|
||||||
|
assert_equal_json("[#{render_gem}] :test_render", expected_json_file, json_document)
|
||||||
|
json_document = test_manual_eagerload(render_gem)
|
||||||
|
assert_equal_json("[#{render_gem}] :test_manual_eagerload", expected_json_file, json_document)
|
||||||
|
end
|
||||||
|
|
||||||
|
require "fileutils"
|
||||||
|
def assert_equal_json(description, expected_json_file, actual_json_document)
|
||||||
|
# 1. in tmp/
|
||||||
|
temp_dir = "tmp"
|
||||||
|
FileUtils.mkdir_p(temp_dir)
|
||||||
|
# 2. write pretty actual json doc
|
||||||
|
actual_json_file = File.join(temp_dir, "actual--#{File.basename(expected_json_file)}")
|
||||||
|
actual_json_document = JSON.pretty_generate(actual_json_document)
|
||||||
|
File.write(actual_json_file, actual_json_document)
|
||||||
|
# 3. if expected json doc missing, copy actual to expected
|
||||||
|
unless File.exist?(expected_json_file)
|
||||||
|
FileUtils.cp(actual_json_file, expected_json_file)
|
||||||
|
end
|
||||||
|
# 4. Check for differences
|
||||||
|
cmd = "diff --suppress-common-lines --side-by-side #{expected_json_file} #{actual_json_file}"
|
||||||
|
diff = `#{cmd}`.chomp
|
||||||
|
# 5. If none, 'true', they are equal
|
||||||
|
# else make a full diff for later review
|
||||||
|
return true if diff.empty?
|
||||||
|
cmd = "diff --side-by-side #{expected_json_file} #{actual_json_file} > #{File.join(temp_dir, "actual--#{File.basename(expected_json_file)}")}.diff"
|
||||||
|
system(cmd)
|
||||||
|
# 6. abort run, print brief diff
|
||||||
|
abort "#{description}. Invalid JSON document.\nDiff:\n#{diff}}"
|
||||||
|
end
|
||||||
|
|
||||||
def test_render(render_gem)
|
def test_render(render_gem)
|
||||||
render_data(
|
render_data(
|
||||||
User.first,
|
User.first,
|
||||||
@ -37,10 +75,14 @@ module BenchHelper
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_data(data, render_gem)
|
# protected
|
||||||
return render_with_ams(data) if render_gem == :ams
|
|
||||||
|
|
||||||
render_with_jsonapi_rb(data)
|
def render_data(data, render_gem)
|
||||||
|
case render_gem
|
||||||
|
when :ams then render_with_ams(data)
|
||||||
|
when :jsonapi_rb then render_with_jsonapi_rb(data)
|
||||||
|
else fail ArgumentError, "Cannot render unknown gem '#{render_gem.inspect}'"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_with_ams(data)
|
def render_with_ams(data)
|
||||||
|
|||||||
1341
benchmarks/serialization_libraries/support/json_document-ams.json
Normal file
1341
benchmarks/serialization_libraries/support/json_document-ams.json
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -36,14 +36,17 @@ class ApplicationRecord < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
class Comment < ApplicationRecord
|
class Comment < ApplicationRecord
|
||||||
|
default_scope { order(:id) }
|
||||||
belongs_to :post
|
belongs_to :post
|
||||||
end
|
end
|
||||||
|
|
||||||
class Post < ApplicationRecord
|
class Post < ApplicationRecord
|
||||||
|
default_scope { order(:id) }
|
||||||
has_many :comments
|
has_many :comments
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
end
|
end
|
||||||
|
|
||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
|
default_scope { order(:id) }
|
||||||
has_many :posts
|
has_many :posts
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user