Skip to content

[scan][trainer] hotfix for xcresulttool spec changes >= XC16b3#22147

Merged
giginet merged 7 commits intofastlane:masterfrom
dokimyj:dokimyj-patch-1
Jul 24, 2024
Merged

[scan][trainer] hotfix for xcresulttool spec changes >= XC16b3#22147
giginet merged 7 commits intofastlane:masterfrom
dokimyj:dokimyj-patch-1

Conversation

@dokimyj
Copy link
Copy Markdown
Contributor

@dokimyj dokimyj commented Jul 17, 2024

Checklist

  • I've run bundle exec rspec from the root directory to see all new and existing tests pass
    • for trainer module, all passed
  • I've followed the fastlane code style and run bundle exec rubocop -a to ensure the code style is valid
  • I see several green ci/circleci builds in the "All checks have passed" section of my PR (connect CircleCI to GitHub if not)
  • I've read the Contribution Guidelines
  • I've updated the documentation if necessary.
    • N/A
  • I've added or updated relevant unit tests.

Motivation and Context

Resolves #22132

From Xcode 16 beta 3, fastlane ios scan started to fail after xctest had finished.
The reason is: There has been a deprecation and stderr in traditional xcrun xcresulttool command usage.

Error details of xcresulttool related portion
...
00:07:00 Error: This command is deprecated and will be removed in a future release, --legacy flag is required to use it.
00:07:00 Usage: xcresulttool get object [--legacy] --path <path> [--id <id>] [--version <version>] [--format <format>]
00:07:00   See 'xcresulttool get object --help' for more information.
...

I tried new way when obtaining xcresult object (xcresulttool get tests or something) but

  • test summary was separated
  • the entire format of the object was totally different
  • test ID for --id option was not showing up in the report

For these reasons, decided to add --legacy if using Xcode 16 beta 3.
It would take quite a time to use the new type of xcresulttool like in the deprecation notice.

Description

used changes of this branch as a fastlane gem and tried run_tests under XC15 and 16.
specs in trainer had nothing to test with this part so I tried scan on both Xcode 15.4 and 16 beta 3

Testing Steps

00:00:16 $ gem uninstall -Iax fastlane
00:00:16 git clone --single-branch -b dokimyj-patch-1 https://github.com/dokimyj/fastlane fastlane-dokimyj
00:00:16 bundle install --path=vendor/bundle

Both environments are in CI, I cloned this single branch and bundle install to use bundle fastlane.

00:06:53 ▸ Testing passed on 'iPhone 15 Plus'
00:06:53 ▸ Test Succeeded
00:06:57 Skipping HTML... only available with `xcodebuild_formatter: 'xcpretty'` right now
00:06:57 Your 'xcodebuild_formatter' doesn't support these 'output_types'. Change your 'output_types' to prevent these warnings from showing...
00:06:57 +-------------------------+
00:06:57 |      Test Results       |
00:06:57 +--------------------+----+
00:06:57 | Number of tests    | 12 |
00:06:57 | Number of failures | 0  |
00:06:57 +--------------------+----+

scan with Xcode 16 beta 3 using this branch

00:08:36 ▸ Testing passed on 'iPhone 15 Plus'
00:08:36 ▸ Test Succeeded
00:08:41 Skipping HTML... only available with `xcodebuild_formatter: 'xcpretty'` right now
00:08:41 Your 'xcodebuild_formatter' doesn't support these 'output_types'. Change your 'output_types' to prevent these warnings from showing...
00:08:42 +-------------------------+
00:08:42 |      Test Results       |
00:08:42 +--------------------+----+
00:08:42 | Number of tests    | 12 |
00:08:42 | Number of failures | 0  |
00:08:42 +--------------------+----+

scan with Xcode 15.4 using this branch

Seems no differences in the result!

@giginet giginet self-requested a review July 23, 2024 07:09
@dokimyj dokimyj changed the title [WIP][scan][trainer] hotfix for xcresulttool spec changes >= XC16b3 [scan][trainer] hotfix for xcresulttool spec changes >= XC16b3 Jul 23, 2024
@dokimyj dokimyj marked this pull request as ready for review July 23, 2024 08:09
Copy link
Copy Markdown
Collaborator

@giginet giginet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

It's better to add specs on https://github.com/fastlane/fastlane/blob/master/trainer/spec/test_parser_spec.rb with stubbing

result_bundle_object_raw = execute_cmd("xcrun xcresulttool get --format json --path #{path}")
# Hotfix: From Xcode 16 beta 3 'xcresulttool get --format json' has been deprecated; '--legacy' flag required to keep on using the command
xcresulttool_cmd = "xcrun xcresulttool get --format json --path #{path}"
xcresulttool_cmd << ' --legacy' if `xcrun --version`.gsub('xcrun version ', '').to_f >= 70.0 # Version of xcrun bundled in Xcode 16 beta 3
Copy link
Copy Markdown
Collaborator

@giginet giginet Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you use xcrun xcresulttool version instead to check backward compatibility?
This behavior depends on xcresulttool version, not xcrun version.

xcresulttool version 23021, format version 3.53 (current)

format version can be used.

It's better to extract the method to parse xcresulttool version

private
def xcresulttool_version
  result = `xcrun xcresulttool version`
  match = result.match(/format version (?<format_version>\d+\.\d+)/)
  match[:format_version]
end

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ DEVELOPER_DIR=/Applications/Xcode_15.4.app xcrun xcresulttool version
xcresulttool version 22608, format version 3.49 (current)

$ DEVELOPER_DIR=/Applications/Xcode_16_beta.app xcrun xcresulttool version
xcresulttool version 23019.2, format version 3.53 (current)

$ DEVELOPER_DIR=/Applications/Xcode_16_beta_2.app xcrun xcresulttool version
xcresulttool version 23019.2, format version 3.53 (current)

$ DEVELOPER_DIR=/Applications/Xcode_16_beta_3.app xcrun xcresulttool version
xcresulttool version 23021, format version 3.53 (current)

Thanks for the information. I have never known there is xcresulttool version command and just accepted suggestion from other.
Pinpointing xcresulttool version would be the best answer. Thanks again!

# Executes xcresulttool to get JSON format of the result bundle object
result_bundle_object_raw = execute_cmd("xcrun xcresulttool get --format json --path #{path}")
# Hotfix: From Xcode 16 beta 3 'xcresulttool get --format json' has been deprecated; '--legacy' flag required to keep on using the command
xcresulttool_cmd = "xcrun xcresulttool get --format json --path #{path}"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to use an array to construct args

args = %W(
  xcrun
  xcresulttool
  get
  --format
  json
  --path
  #{path}
)
args << "--legacy" if xcresulttool_version >= 3.53
execute_cmd(args.join(' '))

@giginet
Copy link
Copy Markdown
Collaborator

giginet commented Jul 23, 2024

I saw the spec implementation; writing tests for these changes is difficult, so we may skip writing specs.

Copy link
Copy Markdown
Collaborator

@giginet giginet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing the PR.

It almost seems to be good.

)

# Version of xcresulttool bundled in Xcode 16 beta 3 was 23021
xcresulttool_version_string = `xcrun xcresulttool version`.split(',')[0]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you note the expected output from xcrun xcresulttool version on the comment?

# xcresulttool version 23021, format version 3.53 (current)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to use regex and the named capture for code readability

match = result.match(/xcresulttool version (?<version>\d+)/)
version = match[:version]&.to_f

Comment on lines +29 to +56
describe "#generate_cmd_parse_xcresult" do
it "appends '--legacy' to command for parse_xcresult on >= Xcode 16 beta 3", requires_xcode: true do
xcresult_sample_path = "./trainer/spec/fixtures/Test.test_result.xcresult"
@model = Trainer::TestParser.new(xcresult_sample_path)
allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return("xcresulttool version 23021, format version 3.53 (current)")
actual = @model.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path} --legacy")
end

it "use traditional command for parse_xcresult on < Xcode 16 beta 3", requires_xcode: true do
xcresult_sample_path = "./trainer/spec/fixtures/Test.test_result.xcresult"
@model = Trainer::TestParser.new(xcresult_sample_path)
allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return("xcresulttool version 22608, format version 3.49 (current)")
actual = @model.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path}")
end
end
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's better to unify test logic with context

Suggested change
describe "#generate_cmd_parse_xcresult" do
it "appends '--legacy' to command for parse_xcresult on >= Xcode 16 beta 3", requires_xcode: true do
xcresult_sample_path = "./trainer/spec/fixtures/Test.test_result.xcresult"
@model = Trainer::TestParser.new(xcresult_sample_path)
allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return("xcresulttool version 23021, format version 3.53 (current)")
actual = @model.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path} --legacy")
end
it "use traditional command for parse_xcresult on < Xcode 16 beta 3", requires_xcode: true do
xcresult_sample_path = "./trainer/spec/fixtures/Test.test_result.xcresult"
@model = Trainer::TestParser.new(xcresult_sample_path)
allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return("xcresulttool version 22608, format version 3.49 (current)")
actual = @model.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path}")
end
end
describe "#generate_cmd_parse_xcresult" do
let(:xcresult_sample_path) { "./trainer/spec/fixtures/Test.test_result.xcresult" }
let(:subject) { Trainer::TestParser.new(xcresult_sample_path) }
before do
allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return(version)
end
context 'with >= Xcode 16 beta 3' do
let(:version) { 'xcresulttool version 23021, format version 3.53 (current)' }
it 'should pass `--legacy`' do
actual = subject.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path} --legacy")
end
end
context 'with < Xcode 16 beta 3' do
let(:version) { 'xcresulttool version 22608, format version 3.49 (current)' }
it 'should not pass `--legacy`' do
actual = subject.send(:generate_cmd_parse_xcresult, xcresult_sample_path)
expect(actual).to eq("xcrun xcresulttool get --format json --path #{xcresult_sample_path}")
end
end
end

Using context to describe the situation is the best practice of rspec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review again!
The only thing I concern is: if I accept the suggestion, allow_all_instance_of(Trainer::TestParser) will also affect execute_cmd, which also uses backtick.
It will result in error by executing version as a command.

Failure/Error: let(:subject) { Trainer::TestParser.new(xcresult_sample_path) }

     JSON::ParserError:
       809: unexpected token at 'xcresulttool version 23021, format version 3.53 (current)'

This might not be the best practice (or maybe anti-pattern), but initiating Trainer::TestParser.new as a instance variable in before block might be inevitable...

    describe "#generate_cmd_parse_xcresult" do
      let(:xcresult_sample_path) { "./trainer/spec/fixtures/Test.test_result.xcresult" }

      before {
        @subject = Trainer::TestParser.new(xcresult_sample_path)
        allow_any_instance_of(Trainer::TestParser).to receive(:`).and_return(version)
      }

...
(same as your suggestion other than using `@subject` for sending call to private methods)

Copy link
Copy Markdown
Collaborator

@giginet giginet Jul 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, my suggestion was wrong.

I confirmed that this snippet works well.

    describe "#generate_cmd_parse_xcresult" do
      let(:xcresult_sample_path) { "./trainer/spec/fixtures/Test.test_result.xcresult" }
      let!(:subject) { Trainer::TestParser.new(xcresult_sample_path) }
      let(:command) { subject.send(:generate_cmd_parse_xcresult, xcresult_sample_path) }

      before do
        allow(File).to receive(:expand_path).with(xcresult_sample_path).and_return(xcresult_sample_path)
        allow_any_instance_of(Trainer::TestParser).to receive(:`).with('xcrun xcresulttool version').and_return(version)
      end

      context 'with >= Xcode 16 beta 3' do
        let(:version) { 'xcresulttool version 23021, format version 3.53 (current)' }
        let(:expected) { "xcrun xcresulttool get --format json --path #{xcresult_sample_path} --legacy" }

        it 'should pass `--legacy`' do
          expect(command).to eq(expected)
        end
      end

      context 'with < Xcode 16 beta 3' do
        let(:version) { 'xcresulttool version 22608, format version 3.49 (current)' }
        let(:expected) { "xcrun xcresulttool get --format json --path #{xcresult_sample_path}" }

        it 'should not pass `--legacy`' do
          expect(command).to eq(expected)
        end
      end
    end

We have to use let! to define subject before the stubbing.

My point is using context to follow the better rspec

@dokimyj
Copy link
Copy Markdown
Contributor Author

dokimyj commented Jul 24, 2024

Amended along with the review comments:

  • Separated method for xcrun xcresulttool get command generation (for Rspec test :) )
  • Judging from xcresulttool version, not xcrun --version
    • Reviewer recommended format version condition, but from Xcode 16 beta it has never changed in beta 3.
  • Constructing argument with %W instead of oneliner string
  • Added Rspec, formatted
  • Some writing style fixes

How I checked if the fixes work:

I tried on my local machine using Xcode 15.4, Xcode 16 beta, and Xcode 16 beta 3.

  • Xcode 15.4
$ DEVELOPER_DIR=/Applications/Xcode_15.4.app bundle exec fastlane ios test
[✔] 🚀

...

| xcode_path                                     | /Applications/Xcode_15.4.app                                      |
+------------------------------------------------+-------------------------------------------------------------------+

...

[09:55:36]: ▸ Test Succeeded
+------------------------+
|      Test Results      |
+--------------------+---+
| Number of tests    | 6 |
| Number of failures | 0 |
+--------------------+---+

...
  • Xcode 16 beta
$ DEVELOPER_DIR=/Applications/Xcode_16_beta.app bundle exec fastlane ios test
[✔] 🚀

...

| xcode_path                                     | /Applications/Xcode_16_beta.app                                   |
+------------------------------------------------+-------------------------------------------------------------------+

...

[09:59:38]: ▸ Test Succeeded
+------------------------+
|      Test Results      |
+--------------------+---+
| Number of tests    | 6 |
| Number of failures | 0 |
+--------------------+---+
...

  • Xcode 16 beta 3
$ DEVELOPER_DIR=/Applications/Xcode_16_beta_3.app bundle exec fastlane ios test
[✔] 🚀

...

| xcode_path                                     | /Applications/Xcode_16_beta_3.app                                 |
+------------------------------------------------+-------------------------------------------------------------------+

...

[10:18:47]: ▸ Test Succeeded
+------------------------+
|      Test Results      |
+--------------------+---+
| Number of tests    | 6 |
| Number of failures | 0 |
+--------------------+---+

...

And... would anyone please restart appveyor/pr to see if CI is green? I got no permission to start that job I think...

Copy link
Copy Markdown
Collaborator

@giginet giginet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank your continuous effort. Looks great for me 🎉

I'll resolve CI error and I'll merge this soon.

Thank you again!


# e.g. DEVELOPER_DIR=/Applications/Xcode_16_beta_3.app
# xcresulttool version 23021, format version 3.53 (current)
match = `xcrun xcresulttool version`.match(/xcresulttool version (?<version>\d+),/)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@dokimyj dokimyj force-pushed the dokimyj-patch-1 branch 2 times, most recently from 4d6738c to 142ca11 Compare July 24, 2024 07:49
@giginet
Copy link
Copy Markdown
Collaborator

giginet commented Jul 24, 2024

Great work! Thank you!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Xcode 16 beta 3 tests fail as xcresulttool get object is deprecated

2 participants