У меня было чертовски время, пытаясь получить фальшивую фреймворк и запустить, что может проверить мою SFTP-службу. Я был знаком с EasyMock, PowerMock и JMockit, но закончил работу с GMock. test ('org.gmock:gmock:0.8.2') { excludes 'junit' }
Ошибка Grails GMock при издевательстве OutputStream
Теперь, когда у меня есть успешный тест на счастливый путь, я пишу свою логику повтора, а затем мои сценарии сбоев. У меня теперь есть две проблемы. Я не могу найти решения на них, поскольку почти все для Grails и GMock мало документированы.
Метод испытания: Я использую пример this blog's SFTP with JCraft's JSch и немного расширил его, чтобы соответствовать моим потребностям. Я беру учетные данные для подключения и имени файла. Я создаю FileOutputStream, а затем подключаюсь к SFTP-серверу. Если я получу исключение, я повторю его nth раз (упрощенный здесь для целей SO).
/**
* Transfers the file from the remote input server to the local output server.
*
* @param fileName
* - the file name
* @param inputFtpCredential
* - the input server
* @param outputFtpCredential
* - the output server
* @param mode
* - the mode for the transfer (defaults to {@link ChannelSftp#OVERWRITE}
* @throws SftpException if any IO exception occurs. Anything other than
* {@link ChannelSftp#SSH_FX_NO_SUCH_FILE SSH_FX_NO_SUCH_FILE} or {@link
* ChannelSftp#SSH_FX_PERMISSION_DENIED SSH_FX_PERMISSION_DENIED} may cause a retry
*/
public void transferRemoteToLocal(String fileName, FtpCredential inputFtpCredential, FtpCredential outputFtpCredential, Integer mode = ChannelSftp.OVERWRITE) {
for (retryCounter in 0 .. maxRetries) {
FileOutputStream output
try {
File file = new File(outputFtpCredential.remoteBaseDir, fileName);
// set stream to append if the mode is RESUME
output = new FileOutputStream(file, (mode == ChannelSftp.RESUME));
/*
* getting the file length of the existing file. This is only used
* if the mode is RESUME
*/
long fileLength = 0
if (file.exists())
fileLength = file.length()
load (output, fileName, inputFtpCredential, mode, fileLength)
// success
return
} catch (exception) {
// if an exception is thrown then retry a maximum number of times
if (retryCounter < maxRetries) {
// let the thread sleep so as to give time for possible self-resets
log.info "Retry number ${retryCounter+1} of file $fileName transfer after $sleepDuration ms"
Thread.sleep(sleepDuration)
mode = ChannelSftp.RESUME
} else {
int exceptionID = (exception instanceof SftpException)?(exception as SftpException).id:0
throw new SftpException(exceptionID, "Max number of file transfer retries ($maxRetries) exceeded on file $fileName", exception)
}
} finally {
if (output != null)
output.close()
}
}
}
def load(OutputStream outputStream, String fileName, FtpCredential ftpCredential, Integer mode, Long fileIndex = 0)
throws SocketException, IOException, SftpException, Exception {
connect(ftpCredential) { ChannelSftp sftp ->
sftp.get(fileName, outputStream, mode, fileIndex)
}
}
Так что это работает в сочетании с методами из блога. Я написал сценарий моего счастливого пути и начал работать с GMock.
public void testSavingRemoteToLocal_Success() throws JSchException {
// Holders for testing
String fileToTransfer = 'test_large_file.txt'
FtpCredential localCredential = new FtpCredential()
// populate credential
FtpCredential remoteCredential = new FtpCredential()
// populate credential
// Mocks
File mockFile = mock(File, constructor(localCredential.remoteBaseDir, fileToTransfer))
mockFile.exists().returns(false)
FileOutputStream mockFOS = mock(FileOutputStream, constructor(mockFile, false))
// connection
JSch mockJSch = mock(JSch, constructor())
Session mockSession = mock(Session)
ChannelSftp mockChannel = mock(ChannelSftp)
mockJSch.getSession(remoteCredential.username, remoteCredential.server, remoteCredential.port).returns(mockSession)
mockSession.setConfig ("StrictHostKeyChecking", "no")
mockSession.password.set(remoteCredential.password)
mockSession.connect().once()
mockSession.openChannel("sftp").returns(mockChannel)
mockChannel.connect()
mockChannel.cd(remoteCredential.remoteBaseDir).once()
// transfer
mockChannel.get (fileToTransfer, mockFOS, ChannelSftp.OVERWRITE, 0)
// finally method mocks
mockChannel.exit()
mockSession.disconnect()
mockFOS.close()
// Test execution
play {
service.transferRemoteToLocal(fileToTransfer, remoteCredential, localCredential)
}
}
Ошибка 1: я сделал простой копировать/вставить и не стал ничего менять, кроме имени метода испытаний, и я получаю следующее сообщение об ошибке:
java.lang.StackOverflowError
at java.lang.ref.SoftReference.get(SoftReference.java:93)
at org.codehaus.groovy.util.ManagedReference.get(ManagedReference.java:41)
at org.codehaus.groovy.util.ManagedConcurrentMap$Entry.isEqual(ManagedConcurrentMap.java:62)
at org.codehaus.groovy.util.AbstractConcurrentMap$Segment.getOrPut(AbstractConcurrentMap.java:91)
at org.codehaus.groovy.util.AbstractConcurrentMap.getOrPut(AbstractConcurrentMap.java:35)
at org.codehaus.groovy.reflection.ClassInfo.getClassInfo(ClassInfo.java:103)
at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:227)
at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:751)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallStaticSite(CallSiteArray.java:59)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.createCallSite(CallSiteArray.java:146)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
at org.codehaus.groovy.runtime.callsite.StaticMetaClassSite.call(StaticMetaClassSite.java:55)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:42)
и это продолжается какое-то время.
Ошибка 2: Затем я решил прокомментировать счастливый путь и выполнить сценарий повтора. Поэтому я стараюсь использовать везде (2), и ему не понравилось .times (2) в конструкторе. Если я этого не делаю, он жалуется, потому что конструктор вызывается дважды, поскольку повтор завершает все, а затем повторно запускает его при повторной попытке.
Я тогда попытался создать два макета всего до отказа, и это бросает какой-то NPE во время создания второго FileOutputStream mock. Кажется, он делает сравнение в файле.
public void testSavingRemoteToLocal_RetryOnce() throws JSchException {
// Holders for testing
String fileToTransfer = 'test_large_file_desktop.txt'
FtpCredential localCredential = new FtpCredential()
// populate credential
FtpCredential remoteCredential = new FtpCredential()
// populate credential
// Mocks
// First loop that fails
File mockFile2 = mock(File, constructor(inputCredential.remoteBaseDir, fileToTransfer))
mockFile2.exists().returns(false)
FileOutputStream mockFIO2 = mock(FileOutputStream, constructor(mockFile2, false))
// connection
JSch mockJSch2 = mock(JSch, constructor())
Session mockSession2 = mock(Session)
mockJSch2.getSession(outputCredential.username, outputCredential.server, outputCredential.port).returns(mockSession2)
mockSession2.setConfig ("StrictHostKeyChecking", "no")
mockSession2.password.set(outputCredential.password)
mockSession2.connect().raises(new SftpException(0, "throw an exception to retry"))
mockSession2.disconnect()
mockFIO2.close()
// second loop that passes
File mockFile = mock(File, constructor(inputCredential.remoteBaseDir, fileToTransfer))
mockFile.exists().returns(false)
FileOutputStream mockFIO = mock(FileOutputStream, constructor(mockFile, true)) // <-- Fails here with a NPE in mockFile.compareTo
// connection
JSch mockJSch = mock(JSch, constructor())
Session mockSession = mock(Session)
ChannelSftp mockChannel = mock(ChannelSftp)
mockJSch.getSession(outputCredential.username, outputCredential.server, outputCredential.port).returns(mockSession)
mockSession.setConfig ("StrictHostKeyChecking", "no")
mockSession.password.set(outputCredential.password)
mockSession.connect()
mockSession.openChannel("sftp").returns(mockChannel)
mockChannel.connect()
mockChannel.cd(outputCredential.remoteBaseDir)
// transfer
mockChannel.get (fileToTransfer, mockFIO, FtpMonitor.getInstance(assetId), ChannelSftp.RESUME, 0)
// finally method mocks
mockChannel.exit()
mockSession.disconnect()
mockFIO.close()
// Test execution
play {
service.sleepDuration = 200
service.sftpCopyFrom(outputCredential, inputCredential, fileToTransfer, assetId)
}
// Assert the results
}
Я обнаружил, что могу по крайней мере создать сценарий сбоя, если у меня есть 'new File (...)' вне цикла. Но это повлияет на мою логику повтора, если файл частично передан? Мне нужно получить текущую длину файла, и я не хочу, чтобы файл хранился в памяти при смене потока. –