Copying Resource Files For Xcode SPM Tests - xcode11

I am new to the Swift Package Manager but with its integration into Xcode 11 it is time to give it a try. I have a new application and SPM library within a new workspace. I have a working library with tests and have successfully imported the library into the application.
I need to extend the SPM library with new tests that parse json files. I have learned that a resources directory feature is not supported. The only workable scheme seems to be a file copy step added to the library build process so that the resource files can be discovered by the executable.
I could figure out how to do this from the command line but not with Xcode running the build and test. There is no Copy Bundle Resources, build phase for swift packages. In fact everything appears to be hidden by Xcode.
I have looked within the SPM for Makefile type files that would allow me to edit default command line actions thereby circumventing Xcode; but I am not seeing them.
Is there some way to interact/control how Xcode 11 builds SPM targets so that I can copy non-code files to test targets?

Got it working!!!
struct Resource {
let name: String
let type: String
let url: URL
init(name: String, type: String, sourceFile: StaticString = #file) throws {
self.name = name
self.type = type
// The following assumes that your test source files are all in the same directory, and the resources are one directory down and over
// <Some folder>
// - Resources
// - <resource files>
// - <Some test source folder>
// - <test case files>
let testCaseURL = URL(fileURLWithPath: "\(sourceFile)", isDirectory: false)
let testsFolderURL = testCaseURL.deletingLastPathComponent()
let resourcesFolderURL = testsFolderURL.deletingLastPathComponent().appendingPathComponent("Resources", isDirectory: true)
self.url = resourcesFolderURL.appendingPathComponent("\(name).\(type)", isDirectory: false)
}
}
Usage:
final class SPMTestDataTests: XCTestCase {
func testExample() throws {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct
// results.
XCTAssertEqual(SPMTestData().text, "Hello, World!")
let file = try Resource(name: "image", type: "png")
let image = UIImage(contentsOfFile: file.url.path)
print(image)
}
}
I found the key of using #file here

This is another workaround to provide access to test resources. Hopefully an answer to the OP's question will be forthcoming.
Using the code below, an extension is created to allow callers to create URL's to test resources like this.
let url = URL(forResource: "payload", type: "json")
This code requires that all resource files be located in a flat directory named "Resources" just under the test target.
// MARK: - ./Resources/ Workaround
// URL of the directory containing non-code, test resource fi;es.
//
// It is required that a directory named "Resources" be contained immediately below the test target.
// Root
// Package.swift
// Tests
// (target)
// Resources
//
fileprivate let _resources: URL = {
func packageRoot(of file: String) -> URL? {
func isPackageRoot(_ url: URL) -> Bool {
let filename = url.appendingPathComponent("Package.swift", isDirectory: false)
return FileManager.default.fileExists(atPath: filename.path)
}
var url = URL(fileURLWithPath: file, isDirectory: false)
repeat {
url = url.deletingLastPathComponent()
if url.pathComponents.count <= 1 {
return nil
}
} while !isPackageRoot(url)
return url
}
guard let root = packageRoot(of: #file) else {
fatalError("\(#file) must be contained in a Swift Package Manager project.")
}
let fileComponents = URL(fileURLWithPath: #file, isDirectory: false).pathComponents
let rootComponenets = root.pathComponents
let trailingComponents = Array(fileComponents.dropFirst(rootComponenets.count))
let resourceComponents = rootComponenets + trailingComponents[0...1] + ["Resources"]
return URL(fileURLWithPath: resourceComponents.joined(separator: "/"), isDirectory: true)
}()
extension URL {
init(forResource name: String, type: String) {
let url = _resources.appendingPathComponent("\(name).\(type)", isDirectory: false)
self = url
}
}

Related

xCode playground and writing files to Documents

I'm trying to test some sqlite database calls through XCode's playground. I start with a database in my Playground's Resources folder and try to move it to the Playgrounds Documents folder, however what happens is that a symbolic link is generated pointing back to the file within the Resources folder so I am unable to write to that file. However, If I figure out where the Documents folder is and then copy the file there by hand from the terminal everything works just fine.
So why does the file manager copy command actually create a sym link to rather than copy? And is there any way to actually make this happen? It seems to only be a problem with the Playground. copy from Resource to Documents works fine in the app itself.
some code to test within the playground...
let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
let docsDir = dirPaths[0]
let destPath = (docsDir as NSString).appendingPathComponent("/data.sqlite")
print(destPath)
let fileMgr = FileManager.default
let srcPath = Bundle.main.path(forResource: "data", ofType:"sqlite")
// This copies the data.sqlite file from Resources to Documents
// ** However in the playground, only a symlink is generated
do {
try fileMgr.copyItem(atPath: srcPath!, toPath: destPath)
} catch let error {
print("Error (during copy): \(error.localizedDescription)")
}
Rod, It's too late but, I figure this out.
Context:
I add one playground to my project in the same workspace
To make the playground works with my project, I create a framework
I added the Store.db file to the framework target
I haven't added the Store.db to the Playground as a Resource
Swift 3
Playground
#testable import MyAppAsFramework
func copyMockDBToDocumentsFolder(dbPath: String) {
let localDBName = "Store.db"
let documentPath = dbPath / localDBName
let dbFile = Bundle(for: MyAppAsFrameworkSomeClass.self).path(forResource: localDBName.deletingPathExtension, ofType: localDBName.pathExtension)
FileManager.copyFile(dbFile, toFolderPath: documentPath)
}
copyMockDBToDocumentsFolder(dbPath: "The documents path")
Things like / copyFile(x) and the others are operator overloads and extensions
extension String {
public var pathExtension: String {
get {
return (self as NSString).pathExtension
}
}
public var deletingPathExtension: String {
get {
return (self as NSString).deletingPathExtension
}
}
public func appendingPath(_ path: String) -> String {
let nsSt = self as NSString
return nsSt.appendingPathComponent(path)
}
}
infix operator / : MultiplicationPrecedence
public func / (left: String, right: String) -> String {
return left.appendingPath(right)
}
extension FileManager {
class func copyFile(_ filePath: String?, toFolderPath: String?) -> Bool {
guard let file = filePath else { return false }
guard let toFolder = toFolderPath else { return false }
var posibleError: NSError?
do {
try FileManager.default.copyItem(atPath: file, toPath:toFolder)
} catch let error as NSError {
posibleError = error
print("CAN'T COPY \(error.localizedDescription)")
}
return posibleError == nil
}
}

cocoalumberjack log to one file

I am developing mac application in that application I need to log to folder, where already some other application is also logging,so need to create only one file in that folder, when file rolling occurs the whole contents in that log folder are deleting .this code I am using .I don't want delete contents in log folder and is it possible to use only file with constant name .Please help me.
// Configure CocoaLumberjack
DDLog.addLogger(DDASLLogger.sharedInstance())
DDLog.addLogger(DDTTYLogger.sharedInstance())
// Initialize File Logger
let manager : BaseLogFileManager = BaseLogFileManager(logsDirectory:K.LogFileDir)
let fileLogger: DDFileLogger = DDFileLogger(logFileManager: manager) // File Logger
fileLogger.maximumFileSize = 1024*1024*20
fileLogger.doNotReuseLogFiles = false
fileLogger.logFileManager.maximumNumberOfLogFiles = 1
DDLog.addLogger(fileLogger)
class BaseLogFileManager : DDLogFileManagerDefault
{
override var newLogFileName: String! { get {
return K.LogFileName
}}
override func isLogFile(fileName: String!) -> Bool
{
return true
}
}
Work around is to disable rolling frequency, don't assign maximum size or rollingFrequency and check size using NSFileManager. If file size is greater than specific limit, remove and create new file.
// Configure CocoaLumberjack
DDLog.addLogger(DDASLLogger.sharedInstance())
DDLog.addLogger(DDTTYLogger.sharedInstance())
// Initialize File Logger
let manager : BaseLogFileManager = BaseLogFileManager(logsDirectory:K.LogFileDir)
let fileLogger: DDFileLogger = DDFileLogger(logFileManager: manager) // File Logger
do {
let attr : NSDictionary? = try NSFileManager.defaultManager().attributesOfItemAtPath(K.LogFileDir+"/"+K.LogFileName)
if let _attr = attr {
if _attr.fileSize() > 1024*1024*10
{
NSFileManager.defaultManager().createFileAtPath(K.LogFileDir+"/"+K.LogFileName, contents: NSData(), attributes: nil)
}
}
} catch {
print("Error: \(error)")
}
fileLogger.doNotReuseLogFiles = false
fileLogger.logFileManager.maximumNumberOfLogFiles = 1
DDLog.addLogger(fileLogger)

Pass CSV file to Swift 2 File for UI Automation Testing

Hi i am absolute beginner to the swift and Xcode tool. I am trying to Test application through UI Testing bundle introduced from Xcode 7+. I want to migrate my hardcoded data from scripts to cvs file. please help.
currently i have created a class csvScanner but it is showing Argument labels(contentsOfFile:,encoding :,error) error in code below
import Foundation
class CSVScanner {
class func debug(string:String){
print("CSVScanner: \(string)")
}
class func runFunctionOnRowsFromFile(theColumnNames:Array<String>, withFileName theFileName:String, withFunction theFunction:(Dictionary<String, String>)->()) {
if let strBundle = NSBundle.mainBundle().pathForResource(theFileName, ofType: "csv") {
var encodingError:NSError? = nil
if let fileObject = NSString(contentsOfFile: strBundle, encoding: NSUTF8StringEncoding, error: &encodingError){
var fileObjectCleaned = fileObject.stringByReplacingOccurrencesOfString("\r", withString: "\n")
fileObjectCleaned = fileObjectCleaned.stringByReplacingOccurrencesOfString("\n\n", withString: "\n")
let objectArray = fileObjectCleaned.componentsSeparatedByString("\n")
for anObjectRow in objectArray {
let objectColumns = anObjectRow.componentsSeparatedByString(",")
var aDictionaryEntry = Dictionary<String, String>()
var columnIndex = 0
for anObjectColumn in objectColumns {
aDictionaryEntry[theColumnNames[columnIndex]] = anObjectColumn.stringByReplacingOccurrencesOfString("\"", withString: "", options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil)
columnIndex++
}
if aDictionaryEntry.count>1{
theFunction(aDictionaryEntry)
}else{
CSVScanner.debug("No data extracted from row: \(anObjectRow) -> \(objectColumns)")
}
}
}else{
CSVScanner.debug("Unable to load csv file from path: \(strBundle)")
if let errorString = encodingError?.description {
CSVScanner.debug("Received encoding error: \(errorString)")
}
}
}else{
CSVScanner.debug("Unable to get path to csv file: \(theFileName).csv")
}
}
}
Thanks in advance
If you are using Xcode 7+, you are also using Swift 2. Instead of passing an error pointer to the method, you need to use try.
The method signature has changed to convenience init(contentsOfFile path: String,
encoding enc: UInt) throws.
(Note: no error:.)

NSURLDownload: Assertion failed ([path isAbsolutePath]) troubles

I'm trying to download a file off the internet and place it in the application name directory under the Application Support directory and I keep getting a
Assertion failed: ([path isAbsolutePath]), function -[NSURLDownload setDestination:allowOverwrite:], file /SourceCache/CFNetwork/CFNetwork-720.5.7/Foundation/NSURLDownload.mm, line 370.
Here's the code that I wrote:
var imageRequest = NSURLRequest(URL: self.source)
var imageDownload = NSURLDownload(request: imageRequest, delegate:self)
var error: NSError? = NSError()
/* does path exist */
let directoryPath = self.destination.stringByDeletingLastPathComponent
let fileMgr = NSFileManager();
fileMgr.createDirectoryAtPath(directoryPath, withIntermediateDirectories: true, attributes: nil, error: &error)
imageDownload.setDestination(self.destination, allowOverwrite: true);
When I step through the code everything looks correct. self.source is
(https:/remoteDomain.com/img/downloadimage.jpg)
a NSURL
self.destination is the full path in my system (file:/Users/ryan/Library/Application%20Support/AppName/downloadimage.jpg)
Any Ideas?
To answer the question to your specific topic:
The error message says that your path is invalid. The right way to create the path for your image is the following:
let fileManager = NSFileManager.defaultManager()
var folder = "~/Library/Application Support/[APPNAME]/someFolder" as NSString
folder = folder.stringByExpandingTildeInPath
if fileManager.fileExistsAtPath(folder as String) == false {
do {
try fileManager.createDirectoryAtPath(folder as String, withIntermediateDirectories: true, attributes: nil)
}
catch {
//Deal with the error
}
}
BUT
#jtbandes is right. You should use NSURLSessionDownloadTask to download your files.
It is part of the Foundation.framework, which is available on OS X, iOS and watchOS.
The reason to use it is that Apple keeps updating this Api to meet the latest standards. For example, you don't need to worry about IPv4 or IPv6 etc. This avoids crashes and weird behavior in your app.
This is how you use it (Swift):
var imageRequest = NSURLRequest(URL: self.source)
let session = NSURLSession.sharedSession()
let downloadTask = session.downloadTaskWithRequest(imageRequest) { (url: NSURL?, response: NSURLResponse?, error: NSError?) -> Void in
//Work with data
}
downloadTask.resume()
Note that url is the path to the downloaded image.

How can I download an mp4 straight to my hard drive in Swift?

I'm using this code to download an mp4 file:
func downloadImageFile() {
let myURLstring = getImageURLCM()
let myFilePathString = "/Users/jack/Desktop/Comics/"+getTitle()+".mp4"
let url = NSURL(string: myURLstring)
let dataFromURL = NSData(contentsOfURL: url!)
let fileManager = NSFileManager.defaultManager()
fileManager.createFileAtPath(myFilePathString, contents: dataFromURL, attributes: nil)
}
But I've noticed that the file actually gets loaded up into my RAM first, before the NSFileManager saves it to my hard drive (based on the Xcode debug session). That's tolerable for smaller files, but most of the files I want to download with this will be at least 1GB.
My main question is: how to make this more RAM friendly?
I've also noticed that I get the spinning wheel of death until the download is finished, so if advice on fixing that would be appreciated as well.
You would be better to go with the system managed download in NSURLSession, specifically NSURLDownloadTask. This way you don't have to worry about memory management of large downloads. From NSURLSession swift file
/*
* download task convenience methods. When a download successfully
* completes, the NSURL will point to a file that must be read or
* copied during the invocation of the completion routine. The file
* will be removed automatically.
*/
func downloadTaskWithURL(url: NSURL, completionHandler: (NSURL?, NSURLResponse?, NSError?) -> Void) -> NSURLSessionDownloadTask?
Example of Use below - copy and paste into new Swift Playground:
import UIKit
import XCPlayground
func downloadFile(filePath: String) {
let url = NSURL(string: filePath)
if let unwrappedURL = url {
let downloadTask = NSURLSession.sharedSession().downloadTaskWithURL(unwrappedURL) { (urlToCompletedFile, reponse, error) -> Void in
// unwrap error if present
if let unwrappedError = error {
print(unwrappedError)
}
else {
if let unwrappedURLToCachedCompletedFile = urlToCompletedFile {
print(unwrappedURLToCachedCompletedFile)
// Copy this file to your destinationURL with
//NSFileManager.defaultManager().copyItemAtURL
}
}
}
downloadTask?.resume()
}
}
downloadFile("http://devstreaming.apple.com/videos/wwdc/2015/711y6zlz0ll/711/711_networking_with_nsurlsession.pdf?dl=1")
XCPSetExecutionShouldContinueIndefinitely()
Simple Example on github here - https://github.com/serendipityapps/NSURLSessionDownloadTaskExample
dataFromURL.writeToFile(myFilePathString, atomically: true)
This is the snippet I use, it writes the loaded data into the file at the given path.

Resources