appmap 0.25.2 → 0.28.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/CHANGELOG.md +30 -0
  4. data/README.md +123 -39
  5. data/exe/appmap +3 -57
  6. data/lib/appmap.rb +51 -32
  7. data/lib/appmap/algorithm/stats.rb +2 -1
  8. data/lib/appmap/class_map.rb +1 -1
  9. data/lib/appmap/command/record.rb +2 -61
  10. data/lib/appmap/cucumber.rb +89 -0
  11. data/lib/appmap/event.rb +6 -6
  12. data/lib/appmap/hook.rb +27 -13
  13. data/lib/appmap/metadata.rb +62 -0
  14. data/lib/appmap/middleware/remote_recording.rb +2 -7
  15. data/lib/appmap/rails/action_handler.rb +2 -2
  16. data/lib/appmap/rails/sql_handler.rb +2 -2
  17. data/lib/appmap/railtie.rb +2 -2
  18. data/lib/appmap/rspec.rb +20 -38
  19. data/lib/appmap/trace.rb +11 -11
  20. data/lib/appmap/util.rb +40 -0
  21. data/lib/appmap/version.rb +1 -1
  22. data/package-lock.json +3 -3
  23. data/spec/abstract_controller4_base_spec.rb +1 -1
  24. data/spec/abstract_controller_base_spec.rb +1 -1
  25. data/spec/fixtures/hook/singleton_method.rb +54 -0
  26. data/spec/fixtures/rails_users_app/Gemfile +1 -0
  27. data/spec/fixtures/rails_users_app/features/api_users.feature +13 -0
  28. data/spec/fixtures/rails_users_app/features/support/env.rb +4 -0
  29. data/spec/fixtures/rails_users_app/features/support/hooks.rb +11 -0
  30. data/spec/fixtures/rails_users_app/features/support/steps.rb +18 -0
  31. data/spec/hook_spec.rb +107 -23
  32. data/spec/rails_spec_helper.rb +2 -0
  33. data/spec/rspec_feature_metadata_spec.rb +2 -0
  34. data/spec/spec_helper.rb +4 -0
  35. data/spec/util_spec.rb +21 -0
  36. data/test/cli_test.rb +2 -15
  37. data/test/cucumber_test.rb +72 -0
  38. data/test/fixtures/cucumber4_recorder/Gemfile +5 -0
  39. data/test/fixtures/cucumber4_recorder/appmap.yml +3 -0
  40. data/test/fixtures/cucumber4_recorder/features/say_hello.feature +5 -0
  41. data/test/fixtures/cucumber4_recorder/features/support/env.rb +5 -0
  42. data/test/fixtures/cucumber4_recorder/features/support/hooks.rb +11 -0
  43. data/test/fixtures/cucumber4_recorder/features/support/steps.rb +9 -0
  44. data/test/fixtures/cucumber4_recorder/lib/hello.rb +7 -0
  45. data/test/fixtures/cucumber_recorder/Gemfile +5 -0
  46. data/test/fixtures/cucumber_recorder/appmap.yml +3 -0
  47. data/test/fixtures/cucumber_recorder/features/say_hello.feature +5 -0
  48. data/test/fixtures/cucumber_recorder/features/support/env.rb +5 -0
  49. data/test/fixtures/cucumber_recorder/features/support/hooks.rb +11 -0
  50. data/test/fixtures/cucumber_recorder/features/support/steps.rb +9 -0
  51. data/test/fixtures/cucumber_recorder/lib/hello.rb +7 -0
  52. data/test/fixtures/rspec_recorder/Gemfile +1 -1
  53. data/test/fixtures/rspec_recorder/spec/decorated_hello_spec.rb +12 -0
  54. data/test/rspec_test.rb +5 -0
  55. metadata +26 -4
  56. data/lib/appmap/command/upload.rb +0 -101
  57. data/spec/fixtures/hook/class_method.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ee5199f1f1f4c05ba3837f3caf04adb9798bb223c3bf0deb71cdd80216181a0
4
- data.tar.gz: 3aeb05be87430862426a25075a236d54d2d20bb0a1d893b71b1eaa63c8c44c01
3
+ metadata.gz: 8619a5924ba51ee48d76ebfe5c72030629c5338217b4fe659cd0a580eaa6ddb9
4
+ data.tar.gz: 0d8b1e8fcd41e0785b362350a53c95c5bc0ba5c628371b7b6216315b7ddcd85e
5
5
  SHA512:
6
- metadata.gz: fc4042fe8d7f56a87b205145fcd9e520beb946d27f1e17f5134f4a5127f7ca9c2ee8645cf8377339c34cf814b4faf348894d91f20d31a68e743ede3d5d863419
7
- data.tar.gz: 3c4dba46e95a77b51030065af14e9987f66294ba396a38ef396b79d86b37540d36229670b170e4f632972b563369184d0fa1a4e5b4c18d99d1deb5243b166014
6
+ metadata.gz: c4da2cadb6a852b1213d1938b035ca147a939e7dc1378ef435d1e8b4d42d6f5b98e19191ecd2e1d1e48493817117123e0b9167080308dcc9ea5527bb676f436b
7
+ data.tar.gz: 04c2a36c913a3eec42dcd8aa61e789b9b91fed1e43b4bfe84729c6a7b4451fb1064c6efdba39e3818674df8f9560caa486e1d4c8170f04682938775fbb2c4d7e
data/.gitignore CHANGED
@@ -1,7 +1,6 @@
1
1
  .DS_Store
2
2
  /html/
3
- /.appmap/
4
- /.bundle/
3
+ .bundle
5
4
  /.yardoc
6
5
  /_yardoc/
7
6
  /coverage/
@@ -1,3 +1,33 @@
1
+ # v0.28.1
2
+ * Fix the `defined_class` recorded in an appmap for an instance method included in a class
3
+ at runtime.
4
+
5
+ * Only include the `static` attribute on `call` events in an appmap. Determine its value
6
+ based on the receiver of the method call.
7
+
8
+ # v0.28.0
9
+
10
+ * Change behavior of **AppMap.record** to return a complete AppMap as a Hash.
11
+ * Update README with information about recording Cucumber tests.
12
+ * **AppMap.initialize** automatically runs when `AppMap` is required, unless disabled
13
+ by environment variable `APPMAP_INITIALIZE=false`.
14
+ * **AppMap.hook** no longer takes a `configuration` argument.
15
+ * Add **AppMap::Util.scenario_filename**.
16
+
17
+ # v0.27.0
18
+
19
+ * Add **AppMap.record** to programatically record and capture an AppMap of a Ruby block.
20
+
21
+ # v0.26.1
22
+
23
+ * Fix a bug that caused duplicate entries in the list of frameworks that appear
24
+ in the `metadata` section of an appmap.
25
+
26
+ # v0.26.0
27
+
28
+ * **appmap upload** is removed. Upload functionality has been moved to
29
+ the [AppLand CLI](https://github.com/applandinc/appland-cli).
30
+
1
31
  # v0.25.2
2
32
 
3
33
  * Stop checking a whitelist to see if each SQL query should be recorded. Record
data/README.md CHANGED
@@ -1,18 +1,23 @@
1
1
  - [About](#about)
2
- - [Testing](#testing)
3
2
  - [Installation](#installation)
4
3
  - [Configuration](#configuration)
5
4
  - [Running](#running)
6
5
  - [RSpec](#rspec)
6
+ - [Cucumber](#cucumber)
7
7
  - [Remote recording](#remote-recording)
8
8
  - [Ruby on Rails](#ruby-on-rails)
9
9
  - [Uploading AppMaps](#uploading-appmaps)
10
+ - [Development](#development)
11
+ - [Running tests](#running-tests)
12
+ - [Using fixture apps](#using-fixture-apps)
13
+ - [`test/fixtures`](#testfixtures)
14
+ - [`spec/fixtures`](#specfixtures)
10
15
  - [Build status](#build-status)
11
16
 
12
17
  # About
13
18
 
14
- `appmap-ruby` is a Ruby Gem for recording and uploading
15
- [AppMaps](https://github.com/applandinc/appmap) of your code.
19
+ `appmap-ruby` is a Ruby Gem for recording
20
+ [AppMaps](https://github.com/applandinc/appmap) of your code.
16
21
  "AppMap" is a data format which records code structure (modules, classes, and methods), code execution events
17
22
  (function calls and returns), and code metadata (repo name, repo URL, commit
18
23
  SHA, labels, etc). It's more granular than a performance profile, but it's less
@@ -22,24 +27,15 @@ There are several ways to record AppMaps of your Ruby program using the `appmap`
22
27
 
23
28
  * Run your RSpec tests with the environment variable `APPMAP=true`. An AppMap will be generated for each spec.
24
29
  * Run your application server with AppMap remote recording enabled, and use the AppMap.
25
- browser extension to start, stop, and upload recordings.
30
+ browser extension to start, stop, and upload recordings.
26
31
  * Run the command `appmap record <program>` to record the entire execution of a program.
27
32
 
28
- When you record AppMaps on the command line (for example, by running RSpec tests), you use the `appmap upload` command to
29
- upload them to the AppLand website. On the AppLand website, you'll be able to
33
+ Once you have recorded some AppMaps (for example, by running RSpec tests), you use the `appland upload` command
34
+ to upload them to the AppLand server. This command, and some others, is provided
35
+ by the [AppLand CLI](https://github.com/applandinc/appland-cli/releases), to
36
+ Then, on the [AppLand website](https://app.land), you can
30
37
  visualize the design of your code and share links with collaborators.
31
38
 
32
- # Testing
33
- Before running tests, configure `local.appmap` to point to your local `appmap-ruby` directory.
34
- ```
35
- $ bundle config local.appmap $(pwd)
36
- ```
37
-
38
- Run the tests via `rake`:
39
- ```
40
- $ bundle exec rake test
41
- ```
42
-
43
39
  # Installation
44
40
 
45
41
  Add `gem 'appmap'` to your Gemfile just as you would any other dependency.
@@ -58,7 +54,7 @@ group :development, :test do
58
54
  end
59
55
  ```
60
56
 
61
- Then install with `bundle`.
57
+ Then install with `bundle`.
62
58
 
63
59
  # Configuration
64
60
 
@@ -88,7 +84,7 @@ Each entry in the `packages` list is a YAML object which has the following keys:
88
84
 
89
85
  ## RSpec
90
86
 
91
- To instrument RSpec tests, follow these additional steps:
87
+ To record RSpec tests, follow these additional steps:
92
88
 
93
89
  1) Require `appmap/rspec` in your `spec_helper.rb`.
94
90
 
@@ -96,28 +92,16 @@ To instrument RSpec tests, follow these additional steps:
96
92
  require 'appmap/rspec'
97
93
  ```
98
94
 
99
- 2) Add `appmap: true` to the tests you want to instrument.
100
-
101
- ```ruby
102
- describe Hello, appmap: true do
103
- describe 'says hello' do
104
- it 'when prompted' do
105
- expect(Hello.new.say_hello).to eq('Hello!')
106
- end
107
- end
108
- end
109
- ```
110
-
111
- 3) *Optional* Add `feature: '<feature name>'` and `feature_group: '<feature group name>'` annotations to your
95
+ 2) *Optional* Add `feature: '<feature name>'` and `feature_group: '<feature group name>'` annotations to your
112
96
  examples.
113
97
 
114
- 4) Run the tests with the environment variable `APPMAP=true`:
98
+ 3) Run the tests with the environment variable `APPMAP=true`:
115
99
 
116
100
  ```sh-session
117
101
  $ APPMAP=true bundle exec rspec -t appmap
118
102
  ```
119
103
 
120
- Each RSpec test will output a data file into the directory `tmp/appmap/rspec`. For example:
104
+ Each RSpec test will output an AppMap file into the directory `tmp/appmap/rspec`. For example:
121
105
 
122
106
  ```
123
107
  $ find tmp/appmap/rspec
@@ -141,6 +125,44 @@ If you include the `feature` and `feature_group` metadata, these attributes will
141
125
 
142
126
  If you don't explicitly declare `feature` and `feature_group`, then they will be inferred from the spec name and example descriptions.
143
127
 
128
+ ## Cucumber
129
+
130
+ To record Cucumber tests, follow these additional steps:
131
+
132
+ 1) Require `appmap/cucumber` in `support/env.rb`:
133
+
134
+ ```ruby
135
+ require 'appmap/cucumber'
136
+ ```
137
+
138
+ 2) Create an `Around` hook in `support/hooks.rb` to record the scenario:
139
+
140
+
141
+ ```ruby
142
+ if AppMap::Cucumber.enabled?
143
+ Around('not @appmap-disable') do |scenario, block|
144
+ appmap = AppMap.record do
145
+ block.call
146
+ end
147
+
148
+ AppMap::Cucumber.write_scenario(scenario, appmap)
149
+ end
150
+ end
151
+ ```
152
+
153
+ 3) Run the tests with the environment variable `APPMAP=true`:
154
+
155
+ ```sh-session
156
+ $ APPMAP=true bundle exec cucumber
157
+ ```
158
+
159
+ Each Cucumber test will output an AppMap file into the directory `tmp/appmap/cucumber`. For example:
160
+
161
+ ```
162
+ $ find tmp/appmap/cucumber
163
+ Hello_Says_hello_when_prompted.appmap.json
164
+ ```
165
+
144
166
  ## Remote recording
145
167
 
146
168
  To manually record ad-hoc AppMaps of your Ruby app, use AppMap remote recording.
@@ -179,13 +201,75 @@ Note that using this method is kind of a blunt instrument. Recording RSpecs and
179
201
 
180
202
  # Uploading AppMaps
181
203
 
182
- To upload an AppMap file to AppLand, run the `appmap upload` command. For example (with binstubs installed):
204
+ For instructions on uploading, see the documentation of the [AppLand CLI](https://github.com/applandinc/appland-cli).
205
+
206
+ # Development
207
+
208
+ ## Running tests
209
+
210
+ Before running tests, configure `local.appmap` to point to your local `appmap-ruby` directory.
211
+ ```
212
+ $ bundle config local.appmap $(pwd)
213
+ ```
214
+
215
+ Run the tests via `rake`:
216
+ ```
217
+ $ bundle exec rake test
218
+ ```
219
+
220
+ ## Using fixture apps
221
+
222
+ ### `test/fixtures`
223
+
224
+ The fixture apps in `test/fixtures` are plain Ruby projects that exercise the basic functionality of the
225
+ `appmap` gem. To develop in a fixture, simple enter the fixture directory and `bundle`.
226
+
227
+ ### `spec/fixtures`
228
+
229
+ The fixture apps in `spec/fixtures` are simple Rack, Rails4, and Rails5 apps.
230
+ You can use them to interactively develop and test the recording features of the `appmap` gem.
231
+ These fixture apps are more sophisticated than `test/fixtures`, because they include additional
232
+ resources such as a PostgreSQL database.
233
+
234
+ To build the fixture container images, first run:
183
235
 
184
236
  ```sh-session
185
- $ ./bin/appmap upload tmp/appmap/rspec/*
186
- Uploading "tmp/appmap/rspec/Hello_app_says_hello_when_prompted.appmap.json"
187
- Scenario Id: 4da4f267-bdea-48e8-bf67-f39463844230
188
- Batch Id: a116f1df-ee57-4bde-8eef-851af0f3d7bc
237
+ $ bundle exec rake fixtures:all
238
+ ```
239
+
240
+ This will build the `appmap.gem`, along with a Docker image for each fixture app.
241
+
242
+ Then move to the directory of the fixture you want to use, and provision the environment.
243
+ In this example, we use Ruby 2.6.
244
+
245
+ ```sh-session
246
+ $ export RUBY_VERSION=2.6
247
+ $ docker-compose up -d pg
248
+ $ sleep 10s # Or some reasonable amount of time
249
+ $ docker-compose run --rm app ./create_app
250
+ ```
251
+
252
+ Now you can start a development container.
253
+
254
+ ```sh-session
255
+ $ docker-compose run --rm -v $PWD/../../..:/src/appmap-ruby app bash
256
+ Starting rails_users_app_pg_1 ... done
257
+ root@6fab5f89125f:/app# cd /src/app
258
+ root@6fab5f89125f:/src/app# bundle config local.appmap /src/appmap-ruby
259
+ root@6fab5f89125f:/src/app# bundle update appmap
260
+ ```
261
+
262
+ At this point, the bundle is built with the `appmap` gem located in `/src/appmap`, which is volume-mounted from the host.
263
+ So you can edit the fixture code and the appmap code and run test commands such as `rspec` and `cucumber` in the container.
264
+ For example:
265
+
266
+ ```sh-session
267
+ root@6fab5f89125f:/src/app# bundle exec rspec
268
+ Configuring AppMap from path appmap.yml
269
+ ....
270
+
271
+ Finished in 0.07357 seconds (files took 2.1 seconds to load)
272
+ 4 examples, 0 failures
189
273
  ```
190
274
 
191
275
  # Build status
data/exe/appmap CHANGED
@@ -3,6 +3,8 @@
3
3
 
4
4
  require 'gli'
5
5
 
6
+ ENV['APPMAP_INITIALIZE'] = 'false'
7
+
6
8
  require 'appmap'
7
9
  require 'appmap/version'
8
10
 
@@ -125,62 +127,6 @@ module AppMap
125
127
  end
126
128
  end
127
129
 
128
- desc 'Upload a scenario file to AppLand.'
129
- arg_name 'filename'
130
- command :upload do |c|
131
- output_file_flag(c, default_value: '-')
132
-
133
- c.desc 'Whether to open the new scenario in the browser'
134
- c.default_value true
135
- c.switch [:open]
136
-
137
- c.desc 'AppLand website URL'
138
- c.default_value ENV['APPLAND_URL'] || 'https://appland-staging.herokuapp.com'
139
- c.flag :url
140
-
141
- # TODO: This will be replaced with proper login
142
- c.desc 'User id to own the scenario'
143
- c.default_value(ENV['APPLAND_USER'])
144
- c.flag :user
145
-
146
- c.desc 'Organization id to own the scenario'
147
- c.default_value(ENV['APPLAND_ORG'])
148
- c.flag :org
149
-
150
- c.action do |_, options, args|
151
- require 'appmap/command/upload'
152
-
153
- filenames = args
154
- help_now!("'filename' argument is required") if filenames.empty?
155
-
156
- url = options[:url]
157
- user = options[:user]
158
- org = options[:org]
159
-
160
- batch_id = nil
161
- uuids = filenames.map do |filename|
162
- appmap = JSON.parse(File.read(filename))
163
-
164
- warn "Uploading #{filename.inspect}"
165
- upload = AppMap::Command::Upload.new(appmap, url, user, org)
166
- upload.batch_id = batch_id if batch_id
167
- upload.perform.tap do |response|
168
- batch_id ||= response.batch_id
169
- @output_file.puts "Scenario Id: #{response.scenario_uuid}"
170
- end.scenario_uuid
171
- end
172
- @output_file.puts "Batch Id: #{batch_id}"
173
-
174
- if options[:open] && STDIN.tty?
175
- if uuids.length == 1
176
- system "open #{url}/scenarios/#{uuids.first}"
177
- else
178
- system "open #{url}/scenario_batches/#{batch_id}"
179
- end
180
- end
181
- end
182
- end
183
-
184
130
  pre do |global, _, options, _|
185
131
  @config = interpret_config_option(global[:config])
186
132
  @output_file = interpret_output_file_option(options[:output])
@@ -192,7 +138,7 @@ module AppMap
192
138
  protected
193
139
 
194
140
  def interpret_config_option(fname)
195
- AppMap.configure fname
141
+ AppMap.initialize fname
196
142
  end
197
143
 
198
144
  def interpret_output_file_option(file_name)
@@ -10,58 +10,77 @@ end
10
10
  require 'appmap/version'
11
11
 
12
12
  module AppMap
13
- BATCH_HEADER_NAME = 'AppLand-Scenario-Batch'
14
-
15
13
  class << self
16
- @config = nil
17
- @config_file_path = nil
14
+ @configuration = nil
15
+ @configuration_file_path = nil
18
16
 
19
- # configuration gets the AppMap configuration.
17
+ # configuration gets the configuration. If there is no configuration, the default
18
+ # configuration is initialized.
20
19
  def configuration
21
- raise "AppMap is not configured" unless @config
22
-
23
- @config
20
+ @configuration ||= configure
24
21
  end
25
22
 
26
- # configure applies the configuration from a file. This method can only be performed once.
27
- # Be sure and call it before +hook+ if you want non-default configuration.
28
- #
29
- # Default behavior is to configure from "appmap.yml".
30
- def configure(config_file_path = 'appmap.yml')
31
- if @config
32
- return @config if @config_file_path == config_file_path
33
-
34
- raise "AppMap is already configured from #{@config_file_path}, can't reconfigure from #{config_file_path}"
35
- end
23
+ # configuration= sets the configuration. This is only expected to happen once per
24
+ # Ruby process.
25
+ def configuration=(config)
26
+ warn 'AppMap is already configured' if @configuration && config
36
27
 
37
- warn "Configuring AppMap from path #{config_file_path}"
38
- require 'appmap/hook'
39
- AppMap::Hook::Config.load_from_file(config_file_path).tap do |config|
40
- @config = config
41
- @config_file_path = config_file_path
42
- end
28
+ @configuration = config
43
29
  end
44
30
 
45
- # Activate the code hooks which record function calls as trace events.
31
+ # initialize configures AppMap for recording. Default behavior is to configure from "appmap.yml".
32
+ # This method also activates the code hooks which record function calls as trace events.
46
33
  # Call this function before the program code is loaded by the Ruby VM, otherwise
47
34
  # the load events won't be seen and the hooks won't activate.
48
- def hook(config = configure)
35
+ def initialize(config_file_path = 'appmap.yml')
36
+ warn "Configuring AppMap from path #{config_file_path}"
49
37
  require 'appmap/hook'
50
- AppMap::Hook.hook(config)
38
+ self.configuration = Hook::Config.load_from_file(config_file_path)
39
+ Hook.hook(configuration)
51
40
  end
52
41
 
53
- # Access the AppMap::Tracers, which can be used to start tracing, stop tracing, and record events.
42
+ # tracing can be used to start tracing, stop tracing, and record events.
54
43
  def tracing
55
44
  require 'appmap/trace'
56
- @tracing ||= Trace::Tracers.new
45
+ @tracing ||= Trace::Tracing.new
57
46
  end
58
47
 
59
- # Build a class map from a config and a list of Ruby methods.
60
- def class_map(config, methods)
48
+ # record records the events which occur while processing a block,
49
+ # and returns an AppMap as a Hash.
50
+ def record
51
+ tracer = tracing.trace
52
+ begin
53
+ yield
54
+ ensure
55
+ tracing.delete(tracer)
56
+ end
57
+
58
+ events = [].tap do |event_list|
59
+ event_list << tracer.next_event.to_h while tracer.event?
60
+ end
61
+ {
62
+ 'version' => AppMap::APPMAP_FORMAT_VERSION,
63
+ 'metadata' => detect_metadata,
64
+ 'classMap' => class_map(tracer.event_methods),
65
+ 'events' => events
66
+ }
67
+ end
68
+
69
+ # class_map builds a class map from a config and a list of Ruby methods.
70
+ def class_map(methods)
61
71
  require 'appmap/class_map'
62
- AppMap::ClassMap.build_from_methods(config, methods)
72
+ ClassMap.build_from_methods(configuration, methods)
73
+ end
74
+
75
+ # detect_metadata returns default metadata detected from the Ruby system and from the
76
+ # filesystem.
77
+ def detect_metadata
78
+ require 'appmap/metadata'
79
+ @metadata ||= Metadata.detect.freeze
80
+ @metadata.deep_dup
63
81
  end
64
82
  end
65
83
  end
66
84
 
67
85
  require 'appmap/railtie' if defined?(::Rails::Railtie)
86
+ AppMap.initialize unless ENV['APPMAP_INITIALIZE'] == 'false'