aruba 0.8.0.pre3 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c4368a692e80cba29cd8cd5d4c072883b8f0416
4
- data.tar.gz: ee245882d6596cd1552dfb1acb5455f7d4cd9cbb
3
+ metadata.gz: 2ddc643d39454c47f130a2fa2cb2dad9f72188ae
4
+ data.tar.gz: 980ee237ac1bcf7d419cbade5f1878676ec6285e
5
5
  SHA512:
6
- metadata.gz: 949e77104c5fe1c9d45047f2351179816add7721326eb3a4b67701a36dec687aa38b6a3a54a7c84d163bb588b8b405704fa46322d5977df1aa57bf5ed1996658
7
- data.tar.gz: 8aac02ac05dcd8ce10f4cb7303b146e930502e087b32b155e0f2a06ea1ee220b7bcbc82ed3cceeb2c62be57cb7de528a213e36b3f92623e6b58dadf8d551c5b8
6
+ metadata.gz: 3237df9ea71f9850509bbda1b4ce5b0fc7288b27f1bf25208ef57acbe45e32c554bbaa4544f76d8e420079a925a16ea0030c0a14fd19803409c670917e1506a7
7
+ data.tar.gz: 34a42f1c3cad83773d435eafc15c828089104991e1e331a0ba221ddd378569cf3888ae49a61316054e5917df26f62f5986e9e6310129aa5e2cee36509c86bbd2
data/Gemfile CHANGED
@@ -41,6 +41,14 @@ group :development, :test do
41
41
  gem 'rspec', '~> 3.3.0'
42
42
  gem 'fuubar', '~> 2.0.0'
43
43
 
44
+ # using platform for this make bundler complain about the same gem given
45
+ # twice
46
+ if RUBY_VERSION < '1.9'
47
+ gem 'cucumber', '~> 1.3.20'
48
+ else
49
+ gem 'cucumber', '~> 2.0'
50
+ end
51
+
44
52
  # Make aruba compliant to ruby community guide
45
53
  platform :ruby_19, :ruby_20, :ruby_21, :ruby_22, :jruby, :rbx do
46
54
  gem 'rubocop', '~> 0.32.0'
data/History.md CHANGED
@@ -1,4 +1,29 @@
1
- ## [v0.8.0](https://github.com/cucumber/aruba/compare/v0.7.4...v0.8.0)
1
+ ## [v0.8.0.pre4](https://github.com/cucumber/aruba/compare/v0.8.0.pre4...v0.8.0.pre5)
2
+ * Build with cucumber 1.3.x on ruby 1.8.7, with cucumber 2.x on all other platforms
3
+ * Fixed bugs in aruba's cucumber steps
4
+ * Disable use of `win32/file`
5
+
6
+ ## [v0.8.0.pre3](https://github.com/cucumber/aruba/compare/v0.8.0.pre2...v0.8.0.pre3)
7
+ * Depend on cucumber 1.3.x for compatibility on ruby 1.8.7
8
+ * Change PWD and OLDPW when `cd('path')` or `cd('path') {}` is used
9
+ * Make nesting of `cd` possible
10
+ * Make `run` inside `cd` possible
11
+ * Fixed some bugs
12
+ * Move `Aruba.proces = InProcess|SpawnProcess|DebugProcess` to `aruba.config`
13
+ * Deprecate direct use of `InProcess|SpawnProcess|DebugProcess`. Now `Command`
14
+ needs to be used
15
+ * Add new configuration options `command_launcher` and `main_klass` for
16
+ deprecation of old-style `Aruba.process = <class>`, `:spawn` is the default
17
+ value for the `command_launcher`-option
18
+ * Added checks for version of `rspec-expectations` to support older `rspec`
19
+ versions like `2.11`
20
+ * Now each `path/to/dir` pushed to `aruba.current_directory` is `pop`ed as whole
21
+ * Make testing of `aruba.current_directory` easier by supporting `end_with?` and `start_with?`
22
+
23
+ ## [v0.8.0.pre2](https://github.com/cucumber/aruba/compare/v0.8.0...v0.8.0.pre2)
24
+ * Relax requirement on rspec-expectations (3.3 -> 2.11)
25
+
26
+ ## [v0.8.0.pre](https://github.com/cucumber/aruba/compare/v0.7.4...v0.8.0.pre)
2
27
  * Make aruba compatible with "ruby 1.8.7" and "ruby 1.9.3" again (fixes #279)
3
28
  * Move more and more documentation to cucumber steps (partly fixes #268)
4
29
  * Refactoring of test suits, now rspec tests run randomly
@@ -38,6 +63,7 @@
38
63
  * Cleanup process management (issue #257)
39
64
  * Make path content available through matchers and api metchods (issue #250)
40
65
  * Refactor announcer to support user defined announce channels (fixes #267)
66
+ * `InProcess` requires that the working directory is determined on runtime not no loadtime
41
67
 
42
68
  ## [v0.6.2](https://github.com/cucumber/aruba/compare/v0.6.1...v0.6.2)
43
69
  * Fixed minor issue #223)
data/aruba.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'aruba'
5
- s.version = '0.8.0.pre3'
5
+ s.version = '0.8.0'
6
6
  s.authors = ["Aslak Hellesøy", "David Chelimsky", "Mike Sassak", "Matt Wynne", "Jarl Friis", "Dennis Günnewig"]
7
7
  s.description = 'Extension for popular TDD and BDD frameworks like "Cucumber" and "RSpec" to make testing commandline applications meaningful, easy and fun.'
8
8
  s.summary = "aruba-#{s.version}"
@@ -10,9 +10,9 @@ Gem::Specification.new do |s|
10
10
  s.email = 'cukes@googlegroups.com'
11
11
  s.homepage = 'http://github.com/cucumber/aruba'
12
12
 
13
- s.add_runtime_dependency 'cucumber', '~> 1.3.19'
13
+ s.add_runtime_dependency 'cucumber', '>= 1.3.19'
14
14
  s.add_runtime_dependency 'childprocess', '~> 0.5.6'
15
- s.add_runtime_dependency 'rspec-expectations', '>= 2.11'
15
+ s.add_runtime_dependency 'rspec-expectations', '>= 2.99'
16
16
  s.add_runtime_dependency 'contracts', '~> 0.9'
17
17
 
18
18
  s.add_development_dependency 'bundler', '~> 1.10.2'
@@ -20,16 +20,34 @@ Gem::Specification.new do |s|
20
20
  s.rubygems_version = ">= 1.6.1"
21
21
  s.required_ruby_version = '>= 1.8.7'
22
22
  s.post_install_message = <<-EOS
23
+ Use on ruby 1.8.7
24
+ * Make sure you add something like that to your `Gemfile`. Otherwise you will
25
+ get cucumber > 2 and this will fail on ruby 1.8.7
26
+
27
+ gem 'cucumber', ~> '1.3.20'
28
+
23
29
  With aruba >= 1.0
24
30
  * "ruby 1.8.7"-support is discontinued.
25
- * aruba requires "cucumber 2" for the feature steps. The rest of aruba should be usable by whatever testing framework you are using.
26
- * Overwriting methods for configuration is discontinued. You need to use `aruba.config.<variable>` or `Aruba.configure { |config| config.<variable>` instead.
27
- * "aruba/reporting" will be removed. Please use `@debug`-tag + `byebug`, `debugger`, `pry` to troubleshoot your feature tests.
28
- * Set environment variables will have only effect on `#run` and the like + `#with_environment { }`.
29
- * The process environment will be fully resetted between tests. Sharing state via ENV['VAR'] = 'shared state' between tests will not be possible anymore. Please make that obvious by using explicit steps or use the aruba API for that.
30
- * There will be a major cleanup for command execution. There will be only `run` and `run_simple` left. `run_interactive` is replaced by `run`.
31
- * Setting the root directory of aruba via method overwrite or configuration - this should be your project root directory where the test suite is run.
32
- * The direct use of "InProcess", "DebugProcess" and "SpawnProcess" is not supported anymore. You need to use "Command" instead. But be careful, it has a different API.
31
+ * aruba requires "cucumber 2" for the feature steps. The rest of aruba should
32
+ be usable by whatever testing framework you are using.
33
+ * Overwriting methods for configuration is discontinued. You need to use
34
+ `aruba.config.<variable>` or `Aruba.configure { |config| config.<variable>`
35
+ instead.
36
+ * "aruba/reporting" will be removed. Please use `@debug`-tag + `byebug`,
37
+ `debugger`, `pry` to troubleshoot your feature tests.
38
+ * Set environment variables will have only effect on `#run` and the like +
39
+ `#with_environment { }`.
40
+ * The process environment will be fully resetted between tests. Sharing state
41
+ via ENV['VAR'] = 'shared state' between tests will not be possible anymore.
42
+ Please make that obvious by using explicit steps or use the aruba API for
43
+ that.
44
+ * There will be a major cleanup for command execution. There will be only
45
+ `run` and `run_simple` left. `run_interactive` is replaced by `run`.
46
+ * Setting the root directory of aruba via method overwrite or configuration -
47
+ this should be your project root directory where the test suite is run.
48
+ * The direct use of "InProcess", "DebugProcess" and "SpawnProcess" is not
49
+ supported anymore. You need to use "Command" instead. But be careful, it has
50
+ a different API.
33
51
  EOS
34
52
 
35
53
  s.files = `git ls-files`.split("\n")
@@ -1,11 +1,39 @@
1
1
  Feature: Run commands in ruby process
2
-
3
2
  Running a lot of scenarios where each scenario uses Aruba
4
3
  to spawn a new ruby process can be time consuming.
5
4
 
6
5
  Aruba lets you plug in your own process class that can
7
6
  run a command in the same ruby process as Cucumber/Aruba.
8
7
 
8
+ We expect that the command supports the following API. It needs to accept:
9
+ argv, stdin, stdout, stderr and kernel on `#initialize` and it needs to have
10
+ an `execute!`-method.
11
+
12
+ ```ruby
13
+ module Cli
14
+ module App
15
+ class Runner
16
+ def initialize(argv, stdin, stdout, stderr, kernel)
17
+ \@argv = argv
18
+ \@stdin = stdin
19
+ \@stdout = stdout
20
+ \@stderr = stderr
21
+ \@kernel = kernel
22
+ end
23
+
24
+ def execute!
25
+ end
26
+ end
27
+ end
28
+ end
29
+ ```
30
+
31
+ The switch to the working directory takes place around the `execute!`-method.
32
+ If needed make sure, that you determine the current working directory within
33
+ code called by the `execute!`-method or just use `Dir.getwd` during "runtime"
34
+ and not during "loadtime", when the `ruby`-interpreter reads in you class
35
+ files.
36
+
9
37
  Background:
10
38
  Given I use a fixture named "cli-app"
11
39
  And a file named "features/support/cli_app.rb" with:
@@ -220,3 +248,53 @@ Feature: Run commands in ruby process
220
248
  """
221
249
  When I run `cucumber`
222
250
  Then the features should all pass
251
+
252
+ Scenario: Set runner via "Aruba.process ="-method and use old class name Aruba::InProcess (deprecated)
253
+ Given a file named "features/support/in_proccess.rb" with:
254
+ """
255
+ require 'aruba/cucumber'
256
+ require 'aruba/in_process'
257
+ require 'aruba/spawn_process'
258
+
259
+ Before('@in-process') do
260
+ Aruba.process = Aruba::InProcess
261
+ Aruba.process.main_class = Cli::App::Runner
262
+ end
263
+
264
+ After('@in-process') do
265
+ Aruba.process = Aruba::SpawnProcess
266
+ end
267
+ """
268
+ Given a file named "lib/cli/app/runner.rb" with:
269
+ """
270
+ module Cli
271
+ module App
272
+ class Runner
273
+ def initialize(argv, stdin, stdout, stderr, kernel)
274
+ @argv = argv
275
+ @stdin = stdin
276
+ @stdout = stdout
277
+ @stderr = stderr
278
+ @kernel = kernel
279
+ end
280
+
281
+ def execute!
282
+ @stdout.puts(@argv.map(&:reverse).join(' '))
283
+ end
284
+ end
285
+ end
286
+ end
287
+ """
288
+ And a file named "features/in_process.feature" with:
289
+ """
290
+ Feature: Run a command in process
291
+ @in-process
292
+ Scenario: Run command
293
+ When I run `reverse.rb Hello World`
294
+ Then the output should contain:
295
+ \"\"\"
296
+ olleH dlroW
297
+ \"\"\"
298
+ """
299
+ When I run `cucumber`
300
+ Then the features should all pass
@@ -0,0 +1,64 @@
1
+ Feature: Check file content
2
+
3
+ Background:
4
+ Given I use a fixture named "cli-app"
5
+
6
+ Scenario: Existing file having content
7
+ Given a file named "features/file_content.feature" with:
8
+ """
9
+ Feature: File content
10
+ Scenario: file content
11
+ Given a file named "test.txt" with:
12
+ \"\"\"
13
+ Hello World
14
+ \"\"\"
15
+ Then the file named "test.txt" should contain:
16
+ \"\"\"
17
+ Hello World
18
+ \"\"\"
19
+ """
20
+ When I run `cucumber`
21
+ Then the features should all pass
22
+
23
+ Scenario: Existing file having content with special characters
24
+ Given a file named "features/file_content.feature" with:
25
+ """
26
+ Feature: File content
27
+ Scenario: file content
28
+ Given a file named "test.txt" with:
29
+ \"\"\"
30
+ UUUUU
31
+
32
+ 1 scenario (1 undefined)
33
+ 5 steps (5 undefined)
34
+
35
+ \"\"\"
36
+ Then the file named "test.txt" should contain:
37
+ \"\"\"
38
+ UUUUU
39
+
40
+ 1 scenario (1 undefined)
41
+ 5 steps (5 undefined)
42
+
43
+ \"\"\"
44
+ """
45
+ When I run `cucumber`
46
+ Then the features should all pass
47
+
48
+ Scenario: Trailing white space is ignored
49
+ Given a file named "features/file_content.feature" with:
50
+ """
51
+ Feature: File content
52
+ Scenario: file content
53
+ Given a file named "test.txt" with:
54
+ \"\"\"
55
+ UUUUU
56
+
57
+ \"\"\"
58
+ Then the file named "test.txt" should contain:
59
+ \"\"\"
60
+ UUUUU
61
+ \"\"\"
62
+ """
63
+ When I run `cucumber`
64
+ Then the features should all pass
@@ -4,7 +4,7 @@ require 'aruba/platform'
4
4
  require 'aruba/process_monitor'
5
5
  require 'aruba/command'
6
6
 
7
- require 'win32/file' if File::ALT_SEPARATOR
7
+ # require 'win32/file' if File::ALT_SEPARATOR
8
8
 
9
9
  module Aruba
10
10
  class << self
@@ -41,10 +41,8 @@ module Aruba
41
41
  def in_current_directory(&block)
42
42
  Aruba::Platform.deprecated('The use of "in_current_directory" deprecated. Use "#cd(\'.\') { # your code }" instead. But be aware, `cd` requires a previously created directory')
43
43
 
44
- create_directory '' unless directory?('.')
45
- cd('', &block)
46
-
47
- self
44
+ create_directory '.' unless directory?('.')
45
+ cd('.', &block)
48
46
  end
49
47
 
50
48
  # @deprecated
@@ -362,10 +360,11 @@ module Aruba
362
360
  end
363
361
 
364
362
  # @deprecated
365
- def in_current_dir(*args, &block)
363
+ def in_current_dir(&block)
366
364
  Aruba::Platform.deprecated('The use of "in_current_dir" is deprecated. Use "#cd(\'.\') { }" instead')
367
365
 
368
- in_current_directory
366
+ create_directory '.' unless directory?('.')
367
+ cd('.', &block)
369
368
  end
370
369
 
371
370
  # @deprecated
@@ -376,17 +376,17 @@ end
376
376
 
377
377
  Then /^(?:a|the) file "([^"]*)" should (not )?contain "([^"]*)"$/ do |file, negated, content|
378
378
  if negated
379
- expect(file).not_to have_file_content Regexp.new(content)
379
+ expect(file).not_to have_file_content Regexp.new(Regexp.escape(content))
380
380
  else
381
- expect(file).to have_file_content Regexp.new(content)
381
+ expect(file).to have_file_content Regexp.new(Regexp.escape(content))
382
382
  end
383
383
  end
384
384
 
385
385
  Then /^(?:a|the) file "([^"]*)" should (not )?contain:$/ do |file, negated, content|
386
386
  if negated
387
- expect(file).not_to have_file_content Regexp.new(content.chomp)
387
+ expect(file).not_to have_file_content Regexp.new(Regexp.escape(content.chomp))
388
388
  else
389
- expect(file).to have_file_content Regexp.new(content.chomp)
389
+ expect(file).to have_file_content Regexp.new(Regexp.escape(content.chomp))
390
390
  end
391
391
  end
392
392
 
@@ -398,6 +398,14 @@ Then /^(?:a|the) file "([^"]*)" should (not )?contain exactly:$/ do |file, negat
398
398
  end
399
399
  end
400
400
 
401
+ Then /^(?:a|the) file "([^"]*)" should (not )?match %r<([^\/]*)>$/ do |file, negated, content|
402
+ if negated
403
+ expect(file).not_to have_file_content Regexp.new(content)
404
+ else
405
+ expect(file).to have_file_content Regexp.new(content)
406
+ end
407
+ end
408
+
401
409
  Then /^(?:a|the) file "([^"]*)" should (not )?match \/([^\/]*)\/$/ do |file, negated, content|
402
410
  if negated
403
411
  expect(file).not_to have_file_content Regexp.new(content)
@@ -54,6 +54,7 @@ module Aruba
54
54
  FileUtils.rm_r(paths, options)
55
55
  end
56
56
 
57
+ # Get current working directory
57
58
  def getwd
58
59
  Dir.getwd
59
60
  end
@@ -6,7 +6,7 @@ module Aruba
6
6
  class DebugProcess < BasicProcess
7
7
  # Use only if mode is :debug
8
8
  def self.match?(mode)
9
- mode == :debug || mode == DebugProcess
9
+ mode == :debug || (mode.is_a?(Class) && mode <= DebugProcess)
10
10
  end
11
11
 
12
12
  # rubocop:disable Metrics/MethodLength
@@ -7,7 +7,7 @@ module Aruba
7
7
  class InProcess < BasicProcess
8
8
  # Use only if mode is in_process
9
9
  def self.match?(mode)
10
- mode == :in_process || mode == InProcess
10
+ mode == :in_process || (mode.is_a?(Class) && mode <= InProcess)
11
11
  end
12
12
 
13
13
  attr_reader :exit_status
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aruba
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.pre3
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aslak Hellesøy
@@ -19,14 +19,14 @@ dependencies:
19
19
  name: cucumber
20
20
  requirement: !ruby/object:Gem::Requirement
21
21
  requirements:
22
- - - "~>"
22
+ - - ">="
23
23
  - !ruby/object:Gem::Version
24
24
  version: 1.3.19
25
25
  type: :runtime
26
26
  prerelease: false
27
27
  version_requirements: !ruby/object:Gem::Requirement
28
28
  requirements:
29
- - - "~>"
29
+ - - ">="
30
30
  - !ruby/object:Gem::Version
31
31
  version: 1.3.19
32
32
  - !ruby/object:Gem::Dependency
@@ -49,14 +49,14 @@ dependencies:
49
49
  requirements:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: '2.11'
52
+ version: '2.99'
53
53
  type: :runtime
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: '2.11'
59
+ version: '2.99'
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: contracts
62
62
  requirement: !ruby/object:Gem::Requirement
@@ -161,6 +161,7 @@ files:
161
161
  - features/steps/commands/run.feature
162
162
  - features/steps/environment/home_variable.feature
163
163
  - features/steps/environment/set_environment_variable.feature
164
+ - features/steps/filesystem/file_content.feature
164
165
  - features/support/aruba.rb
165
166
  - features/support/env.rb
166
167
  - features/support/jruby.rb
@@ -274,16 +275,34 @@ licenses:
274
275
  - MIT
275
276
  metadata: {}
276
277
  post_install_message: |
278
+ Use on ruby 1.8.7
279
+ * Make sure you add something like that to your `Gemfile`. Otherwise you will
280
+ get cucumber > 2 and this will fail on ruby 1.8.7
281
+
282
+ gem 'cucumber', ~> '1.3.20'
283
+
277
284
  With aruba >= 1.0
278
285
  * "ruby 1.8.7"-support is discontinued.
279
- * aruba requires "cucumber 2" for the feature steps. The rest of aruba should be usable by whatever testing framework you are using.
280
- * Overwriting methods for configuration is discontinued. You need to use `aruba.config.<variable>` or `Aruba.configure { |config| config.<variable>` instead.
281
- * "aruba/reporting" will be removed. Please use `@debug`-tag + `byebug`, `debugger`, `pry` to troubleshoot your feature tests.
282
- * Set environment variables will have only effect on `#run` and the like + `#with_environment { }`.
283
- * The process environment will be fully resetted between tests. Sharing state via ENV['VAR'] = 'shared state' between tests will not be possible anymore. Please make that obvious by using explicit steps or use the aruba API for that.
284
- * There will be a major cleanup for command execution. There will be only `run` and `run_simple` left. `run_interactive` is replaced by `run`.
285
- * Setting the root directory of aruba via method overwrite or configuration - this should be your project root directory where the test suite is run.
286
- * The direct use of "InProcess", "DebugProcess" and "SpawnProcess" is not supported anymore. You need to use "Command" instead. But be careful, it has a different API.
286
+ * aruba requires "cucumber 2" for the feature steps. The rest of aruba should
287
+ be usable by whatever testing framework you are using.
288
+ * Overwriting methods for configuration is discontinued. You need to use
289
+ `aruba.config.<variable>` or `Aruba.configure { |config| config.<variable>`
290
+ instead.
291
+ * "aruba/reporting" will be removed. Please use `@debug`-tag + `byebug`,
292
+ `debugger`, `pry` to troubleshoot your feature tests.
293
+ * Set environment variables will have only effect on `#run` and the like +
294
+ `#with_environment { }`.
295
+ * The process environment will be fully resetted between tests. Sharing state
296
+ via ENV['VAR'] = 'shared state' between tests will not be possible anymore.
297
+ Please make that obvious by using explicit steps or use the aruba API for
298
+ that.
299
+ * There will be a major cleanup for command execution. There will be only
300
+ `run` and `run_simple` left. `run_interactive` is replaced by `run`.
301
+ * Setting the root directory of aruba via method overwrite or configuration -
302
+ this should be your project root directory where the test suite is run.
303
+ * The direct use of "InProcess", "DebugProcess" and "SpawnProcess" is not
304
+ supported anymore. You need to use "Command" instead. But be careful, it has
305
+ a different API.
287
306
  rdoc_options:
288
307
  - "--charset=UTF-8"
289
308
  require_paths:
@@ -295,15 +314,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
295
314
  version: 1.8.7
296
315
  required_rubygems_version: !ruby/object:Gem::Requirement
297
316
  requirements:
298
- - - ">"
317
+ - - ">="
299
318
  - !ruby/object:Gem::Version
300
- version: 1.3.1
319
+ version: '0'
301
320
  requirements: []
302
321
  rubyforge_project:
303
322
  rubygems_version: 2.4.5
304
323
  signing_key:
305
324
  specification_version: 4
306
- summary: aruba-0.8.0.pre3
325
+ summary: aruba-0.8.0
307
326
  test_files:
308
327
  - features/api/cd.feature
309
328
  - features/api/command/run.feature
@@ -355,6 +374,7 @@ test_files:
355
374
  - features/steps/commands/run.feature
356
375
  - features/steps/environment/home_variable.feature
357
376
  - features/steps/environment/set_environment_variable.feature
377
+ - features/steps/filesystem/file_content.feature
358
378
  - features/support/aruba.rb
359
379
  - features/support/env.rb
360
380
  - features/support/jruby.rb