MakeAWishFoundation / SwiftyMocky

Framework for automatic mock generation. Adds a set of handy methods, simplifying testing. One of the best and most complete solutions, including generics support and much more.
MIT License
1.04k stars 106 forks source link

Mock generation failure for Swift Package when using actor objects in package #336

Closed MarioJ94 closed 1 year ago

MarioJ94 commented 1 year ago

We are experiencing an issue when generating the mocks for one of our Swift Packages.

Our current project is a workspace with several .pbxproj (main and pods) and also some packages added through SPM.

It began when we added the usage of actors. We just deprecated support of iOS 12 and started using async await. When we generate the mocks only with that, the process is completed modifying the code related to the protocols' methods changed to be async.

We also decided to start using actors. It seems to be working fine when an actor object is defined inside the main .pbxproj source files, but when an actor object is defined in the code inside the Package, it throws the following error:

❌  Error: ShellOut encountered an error
Status code: 3
Message: "/Users/userName/Workspace/Repositories/repoName/app/Packages/PackageName/.mocky0B58A94B-8119-4611-95B6-0C980935A872/.template.swifttemplate: 2023-01-10 11:25:06.314 zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAcxWnbQNG8%3D[31687:9629548] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (SwiftActor) for key (NS.objects) because no class named "SwiftActor" was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target). If the class was renamed, use setClassName:forClass: to add a class translation mapping to NSKeyedUnarchiver'
*** First throw call stack:
(
    0   CoreFoundation                      0x00007ff81b8b243b __exceptionPreprocess + 242
    1   libobjc.A.dylib                     0x00007ff81b401e25 objc_exception_throw + 48
    2   Foundation                          0x00007ff81cbb90bf -[NSCoder __failWithException:] + 165
    3   Foundation                          0x00007ff81c71add7 -[NSCoder(Exceptions) __failWithExceptionName:errorCode:format:] + 378
    4   Foundation                          0x00007ff81c642bf1 _decodeObjectBinary + 654
    5   Foundation                          0x00007ff81c645262 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1752
    6   Foundation                          0x00007ff81c65c92d -[NSArray(NSArray) initWithCoder:] + 161
    7   Foundation                          0x00007ff81c6434b8 _decodeObjectBinary + 2901
    8   Foundation                          0x00007ff81c642842 _decodeObject + 152
    9   Foundation                          0x00007ff81c64272d -[NSKeyedUnarchiver decodeObjectForKey:] + 162
    10  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x00000001068ed606 $sSo7NSCoderC15SourceryRuntimeE11maybeDecode33_59ED7E9A0473E0D69ACA0F3EDA062D19LL6forKeyxSgSS_tlF + 198
    11  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x00000001068eda7c $sSo7NSCoderC15SourceryRuntimeE6decode6forKeyxSgSS_tlF + 44
    12  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x000000010695dd18 $s15SourceryRuntime16FileParserResultC5coderACSgSo7NSCoderC_tcfc + 1080
    13  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x000000010695f278 $s15SourceryRuntime16FileParserResultC5coderACSgSo7NSCoderC_tcfcTo + 40
    14  Foundation                          0x00007ff81c6434b8 _decodeObjectBinary + 2901
    15  Foundation                          0x00007ff81c642842 _decodeObject + 152
    16  Foundation                          0x00007ff81c64272d -[NSKeyedUnarchiver decodeObjectForKey:] + 162
    17  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x00000001068ed606 $sSo7NSCoderC15SourceryRuntimeE11maybeDecode33_59ED7E9A0473E0D69ACA0F3EDA062D19LL6forKeyxSgSS_tlF + 198
    18  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x00000001068eda7c $sSo7NSCoderC15SourceryRuntimeE6decode6forKeyxSgSS_tlF + 44
    19  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x0000000106984625 $s15SourceryRuntime15TemplateContextC5coderACSgSo7NSCoderC_tcfc + 277
    20  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x0000000106984d78 $s15SourceryRuntime15TemplateContextC5coderACSgSo7NSCoderC_tcfcTo + 40
    21  Foundation                          0x00007ff81c6434b8 _decodeObjectBinary + 2901
    22  Foundation                          0x00007ff81c642842 _decodeObject + 152
    23  Foundation                          0x00007ff81c64272d -[NSKeyedUnarchiver decodeObjectForKey:] + 162
    24  Foundation                          0x00007ff81c74d8b1 +[NSKeyedUnarchiver unarchiveObjectWithFile:] + 138
    25  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x0000000106986983 $sSo13NSProcessInfoC15SourceryRuntimeE7contextAC15TemplateContextCSgvg + 227
    26  zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAc 0x00000001069b8d43 main + 4323
    27  dyld                                0x00007ff81b42e310 start + 2432

libc++abi: terminating with uncaught exception of type NSException"
Output: "🌱 Finding latest version of Sourcery
🌱 Running sourcery 1.9.2...
Using configuration file at '/Users/userName/Workspace/Repositories/repoName/app/Packages/PackageName/.mocky0B58A94B-8119-4611-95B6-0C980935A872/.config.yml.tmp'
Scanning sources...
Found 792 types in 581 files, 1 changed from last run.
Files changed:
/Users/userName/Workspace/Repositories/repoName/app/Packages/PackageName/Sources/.../FileWithActorDefined.swift
Loading templates...
Loaded 1 templates.
Generating code...
error: /Users/userName/Workspace/Repositories/repoName/app/Packages/PackageName/.mocky0B58A94B-8119-4611-95B6-0C980935A872/.template.swifttemplate: 2023-01-10 11:25:06.314 zS3a4gNX3EPugaiCq4oLj3Jfvo4zLtXIBAcxWnbQNG8%3D[31687:9629548] *** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (SwiftActor) for key (NS.objects) because no class named "SwiftActor" was found; the class needs to be defined in source code or linked in from a library (ensure the class is part of the correct target). If the class was renamed, use setClassName:forClass: to add a class translation mapping to NSKeyedUnarchiver'

[...]

As you can see, the message talks about an undefined SwiftActor.

Also, I would like to point out that we have noticed that the Sourcery version used when generating the Package's code mocks does not match the version that the pod is supposed to be using. We are using the SwiftyMocky pod with version 4.1.0, which is supposed to use Sourcery v1.6.0 if I am not mistaken, but when running the generate command for the package, the Sourcery version displayed in the logs is 1.9.2 (just adding this information in case it is relevant).

We are happy with finding a solution about the SwiftActor issue. Is this some kind of limitation or maybe we have something wrong?

choulepoka commented 1 year ago

I had a similar issues, and here is what i do

1st thing - Custom Sourcery

I use a custom sourcery, installed directly using Homebrew

brew install sourcery

In my mockfile, i add at the top:

sourceryCommand: sourcery
sourceryTemplate: null

Now it is using the latest sourcery

2nd: sed commands

I execute 2 sed commands that updates the generated code to be @unchecked Sendable

Here is the snippet of code I use to generate my mocks

mint run swiftymocky autoimport
mint run swiftymocky generate
find . -type f -name Mock.generated.swift -exec sed -i '' "s/Mock {/Mock, @unchecked Sendable {/g" {} \;
find . -type f -name Mock.generated.swift -exec sed -i '' "s/enum MethodType {/enum MethodType: @unchecked Sendable {/g" {} \;

Hope this helps!

MarioJ94 commented 1 year ago

Hi @choulepoka! Thanks for the response! First of all, mock generation is now working thanks to your recommendations!

Let me explain what we had: We have two Mockfile files, the main Mockfile for the app for the .pbxproj ([...]/app) and another for the Package ([...]/app/Packages/PackageName). In our main (app) Mockfile we have

sourceryCommand: null
sourceryTemplate: null

This makes it use Sourcery v1.6.0 when generating the app mocks, which is the dependency we are including in our Podfile for the tests target

def unit_testing_pods
  pod 'SwiftyMocky', '4.1.0'
  pod 'Sourcery', '1.6.0'
end

On the other hand, in our Package's Mockfile we had...

sourceryCommand: mint run sourcery
sourceryTemplate: null

... which forced the usage of Sourcery 1.9.2 since it always checks for last version. Changing it to sourceryCommand: sourcery makes it use Sourcery 1.8.2 because it is the one I have globally linked.

🌱 Installed mint packages:
  Sourcery
    - 1.6.0
    - 1.8.2 *
    - 1.9.0
    - 1.9.1
    - 1.9.2

Changing it to 1.6.0 (the one used for the app's Mockfile) makes it fail, which I will try to understand at some point.

Running at: /Users/userName/Workspace/Repositories/repoName/app/Packages/PackageName
Using template from SwiftPM
Processing mock: PackageNameTests ...
Using sourcery command: sourcery
❌  Error: ShellOut encountered an error
Status code: 134
Message: "dyld[76332]: Library not loaded: @rpath/lib_InternalSwiftSyntaxParser.dylib
  Referenced from: <B097698B-C1F6-34E5-8843-A28955C3ECFA> /Users/userName/.mint/packages/github.com_krzysztofzablocki_Sourcery/build/1.6.0/sourcery
  Reason: tried: '/Users/userName/.mint/packages/github.com_krzysztofzablocki_Sourcery/build/1.6.0/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Users/userName/.mint/packages/github.com_krzysztofzablocki_Sourcery/build/1.6.0/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS@rpath/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Users/userName/.mint/packages/github.com_krzysztofzablocki_Sourcery/build/1.6.0/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Users/userName/.mint/packages/github.com_krzysztofzablocki_Sourcery/build/1.6.0/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift-5.5/macosx/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/usr/local/lib/lib_InternalSwiftSyntaxParser.dylib' (no such file), '/usr/lib/lib_InternalSwiftSyntaxParser.dylib' (no such file, not in dyld cache)
/bin/bash: line 1: 76332 Abort trap: 6           sourcery --config "/Users/userName/Workspace/Repositories/repoName/app/Packages/PackageNameTests/.mocky5FCB09FA-7382-47FC-A33B-3552795AD436/.config.yml.tmp""
Output: ""

But now that I take a look at the Mockfile format explained here https://github.com/MakeAWishFoundation/SwiftyMocky/blob/master/guides/Mockfile.md, I guess that (since we have both the Package and the app code in the same repository) we should just have a single Mockfile with the different Mock configurations.

Summary:

I will take this day and the next to check if everything works properly before marking this as closed to ensure I only needed to define the Mockfile properly without having to perform any other command.

Thanks @choulepoka !!!

MarioJ94 commented 1 year ago

Using the autoimport seems to include some imports. With those imports, latest version of Sourcery v1.9.2 seems able to generate the mocks, so there is no need to change the Mockfile from sourceryCommand: mint run sourcery to sourceryCommand: sourcery, although we sometimes got some errors, re-running it would make it work.

With that said, as you suggested, we will use the local sourcery version because we think using the local version helps with stability. Thank you very much for your help!