Окончательное редактирование: Нашел решение проблемы (внизу вопроса).
У меня проблема с Nunit, которая меня огорчает. Редактировать: на самом деле больше похоже на проблему SQLite, но я еще не уверен на 100%.
My TestFixture имеет настройку, которая генерирует случайное имя файла, которое используется в качестве базы данных SQLite в каждом из моих тестов.
[Setup]
public void Setup()
{
// "filename" is a private field in my TestFixture class
filename = ...; // generate random filename
}
Каждый из моих тестов использует эту конструкцию в каждом методе доступа к базе данных:
[Test]
public void TestMethod()
{
using (var connection = Connect())
{
// do database activity using connection
// I've tried including this line but it doesn't help
// and is strictly unnecessary:
connection.Close();
}
}
private DbConnection Connect()
{
var connection = DbProviderFactories.GetFactory("System.Data.SQLite").CreateConnection();
connection.ConnectionString = "Data Source = " + filename;
connection.Open();
return connection;
}
Так что один вспомогательный метод Connect() используется всеми методами. Я предполагаю, что конструкция using() { } вызывает Dispose() в соединении в конце TestMethod() и освобождает соединение с файлом базы данных SQLite.
У меня проблема в моем методе [TearDown]:
[TearDown]
public void Cleanup()
{
File.Delete(filename); // throws an IOException!
}
С каждым тестом я получаю исключение:
System.IO.IOException: The process cannot access the file 'testdatabase2008-12-17_1030-04.614065.sqlite' because it is being used by another process.
Все тесты терпят неудачу, когда попадают в [TearDown], поэтому я получаю каталог, полный временных файлов базы данных (по одному для каждого теста, каждый с другим именем) и целую кучу неудачных тестов.
Какой процесс обращается к файлу? Я не понимаю, как второй процесс может получить доступ к файлу. connection полностью вышел за рамки и был Dispose () d к тому времени, когда я пытаюсь удалить файл, поэтому это не может быть чем-то связанным с SQLite. Может это?
Обратите внимание, что я получаю тот же результат, если запускаю все тесты или только один тест.
Обновлять: Итак, я также попробовал Dispose () для моих объектов DbCommand, поскольку я этого не делал (я предполагал, что любой другой провайдер ADO.NET, который Dispose () использует DbConnection, также Dispose () выполняет любые команды в этом соединении. ) Итак, теперь они выглядят так:
[Test]
public void TestMethod()
{
using (var connection = Connect())
{
using (var command = connection.CreateCommand())
{
// do database activity using connection
}
}
}
Это не имело никакого значения - строка File.Delete () по-прежнему выдает исключение IOException. :-(
Если я удалю эту строку в [TearDown], все мои тесты пройдут, но у меня останется целая куча временных файлов базы данных.
Еще одно обновление: Это прекрасно работает:
var filename = "testfile.sqlite";
using (var connection = BbProviderFactories.GetFactory("System.Data.SQLite").CreateConnection())
{
connection.ConnectionString = "Data Source = " + filename;
connection.Open();
var createCommand = connection.CreateCommand();
createCommand.CommandText =
"CREATE TABLE foo (id integer not null primary key autoincrement, bar text not null);";
createCommand.ExecuteNonQuery();
var insertCommand = connection.CreateCommand();
insertCommand.CommandText = "INSERT INTO foo (bar) VALUES (@bar)";
insertCommand.Parameters.Add(insertCommand.CreateParameter());
insertCommand.Parameters[0].ParameterName = "@bar";
insertCommand.Parameters[0].Value = "quux";
insertCommand.ExecuteNonQuery();
}
File.Delete(filename);
Я не понимаю!
Обновлять: Найдено решение:
[TearDown]
public void Cleanup()
{
GC.Collect();
File.Delete(filename);
}
Я запустил модульные тесты через отладчик, и когда запускается метод [TearDown], определенно больше нет ссылок на SQLite DbConnection. Однако принудительный сборщик мусора должен их очистить. В SQLite должна быть ошибка.
Ваш метод сбора мусора у меня тоже сработал, спасибо.





попробуйте вызвать Close на dbconnection
убедитесь, что процесс sqllite завершен
вы можете увидеть, какой процесс заблокировал ваш файл с помощью утилиты Unlocker (бесплатно)
это может быть "известная" проблема с SqlLite; подшучивание на форуме предлагает закрыть соединение и удалить команду, а также предполагает, что это будет исправлено в будущей версии (поскольку такое поведение не согласуется с другими поставщиками ADO)
Его предложение using должно вызывать Dispose, который вызывает Close. Но все же попробовать стоит!
Вызов Close () не помогает - я получаю точно такой же результат.
@ [Стюарт Джонсон]: см. Правки; Unlocker сообщит вам, у кого открыт ваш файл, вероятно, это SQLLite ...
Есть ли способ заставить SQLite освободить файл? Я пробовал Close (), и DbConnection выходит за рамки, я не уверен, что еще делать.
@ [Стюарт Джонсон]: вы проверили с помощью Unlocker, что Sqlite все еще держит файл открытым?
@ [Стюарт Джонсон]: это может быть известная проблема с / Sqlite, см. Правки
@Steven: похоже, люди в этом потоке не вызывают Dispose () и не получают проблем с блокировкой файлов. Я являюсь вызываю Dispose () - в закрывающей скобке моего using (). Если я не правильно читаю эту ветку?
@ [Стюарт Джонсон]: как я читал поток, вам также необходимо вызвать Dispose для объекта Command, используя соединение, из-за ошибки в поставщике SqlLite
Хорошо, я попробую (когда я дома - кода нет на работе). Спасибо.
Итак, я обернул все свои DbCommands в блоки using (), так что они были Dispose () d непосредственно перед подключением Dispose () d. Это не имело значения - та же проблема. :-(
@ [Стюарт Джонсон]: Тогда я называю "баг" в SqLite. Вы можете попробовать воспользоваться списком рассылки разработчиков sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev или заплатить за поддержку, или, если это открытый исходный код, отлаживать @ # $% самостоятельно. Извините, я ничем не могу помочь, но мой гугл-фу исчерпан!
Я нашел решение! Добавление GC.Collect () непосредственно перед строкой File.Delete в моем методе [TearDown] избавляет от исключений. Когда я отлаживаю, больше нет ссылок на соединение, поэтому я не уверен, как работает GC.Collect, но это так.
@ [Стюарт Джонсон]: Я рад, что это сработало для вас, но я думаю, что это в значительной степени доказывает, что это ошибка в SQLite - удерживать блокировку файла до тех пор, пока не будет завершен объект, на который нет ссылки, - это бааааад!
Разборка выполняется после каждого теста.
This attribute is used inside a TestFixture to provide a common set of functions that are performed after each test method is run.
Вы должны попытаться удалить их все с помощью TestFixtureTearDown:
[TestFixtureTearDown]
public void finish()
{
//Delete all file
}
Возможно, в одном тесте используется файл, который вы пытаетесь удалить в другом тесте. <- [Стюарт] Как я уже сказал в вопросе, это происходит, когда я запускаю только один тест, поэтому это невозможно.
Обновлять Вы не предоставляете достаточно информации о том, что делаете. Вы пытались очистить весь тестовый файл только с одним тестом в своем тестовом файле и попробовать его? [Стюарт] Да. Если это работает [Стюарт] (это не так), значит, у вас есть несколько тестовых задач (они обращаются друг к другу). Вам нужно сократить проблему, чтобы найти источник. Тогда возвращайтесь сюда, мы вам поможем. на данный момент это только предположение, что мы можем вам дать. [Стюарт] Я уже сделал эти сокращения проблемы, которую вы предлагаете, вы обнаружите, что они в моем исходном вопросе!
Я понимаю, что [TearDown] выполняется после каждого теста - это именно то, что я хочу. Я не веду огромный список всех имен файлов, поскольку каждое из них используется ровно для одного теста, и я хочу удалить его в конце теста. Каким образом использование [TestFixtureTearDown] будет иметь значение?
«Возможно, в одном тесте используется файл, который вы пытаетесь удалить в другом тесте». Невозможно - см. Мой последний комментарий в моем вопросе: я получаю тот же результат, даже когда я запускаю каждый тест отдельно.
Вы не предоставляете достаточно информации о том, что делаете. По крайней мере, попробуйте то, что мы вам предлагаем. Вы пытались очистить весь тестовый файл с помощью только одного теста и попробовать его? Если это работает, значит, у вас несколько тестовых задач (они обращаются друг к другу). Плохая деталь вопроса, неправильный ответ, извините.
Да, я пробовал запустить один тест и [TearDown] и [TestFixtureTearDown] удалили файл, и у меня возникла точно такая же проблема. Думаю, в моем вопросе много деталей. Что еще нужно знать?
Во-первых, успокойся. Если бы у вас было столько хороших деталей, проблема уже была бы решена. Во-вторых, голосование "против" не решит вашу проблему. Вы только что потеряли того, кто был готов вам помочь.
Во-вторых, подробнее о том, работает ли это в вашей основной программе? Что за запрос? Вы говорите, что он потерпел неудачу, а затем отправился на разборку, что произойдет при успешном запросе? и т.п.
@ Даок - Я не совсем понимаю, о чем ты говоришь. Я спокоен. Вы говорите мне, что в вопросе недостаточно подробностей, но вы не говорите мне, что еще вам нужно. Удалить () не удается независимо от того, какой запрос выполняется. Как вы думаете, почему это я проголосовал против вас?
@Daok: «Ты говоришь, что он провалился, а потом разбирайся» - я этого не говорил. Все работает Кроме той единственной строчкой в [TearDown].
Мне нужно увидеть логику создания вашего имени файла, но возможно, что вы открываете файл для его создания, но не закрываете его после его создания. Я думаю, что если вы используете System.IO.Path.GetTempFileName (), он просто создаст файл и вернет вам имя файла с закрытым файлом. Если вы генерируете собственное случайное имя и используете File.Open, вам нужно будет убедиться, что он закрыт после этого.
С другой стороны, я настоятельно рекомендую следовать стратегии имитации для абстрагирования базы данных, а не чтения / записи из реальной базы данных в ваших модульных тестах. Суть модульных тестов в том, что они должны быть очень быстрыми, а файловый ввод-вывод действительно их замедлит.
Логика создания файла уже есть - в вызове connection.Open (). SQLite создает для меня файл.
У вас работает антивирус? Некоторые антивирусные продукты сканируют файл, когда видят, что он закрывается. Если в программном обеспечении AV файл все еще открыт, когда вы приходите, чтобы удалить его, вы увидите эту проблему. Вы можете повторить попытку удаления с небольшой задержкой.
Я также видел, как это происходило с поисковыми индексаторами.
Неа. Ни AV, ни поисковых индексаторов.
Первым шагом будет определение того, кто держит дескриптор рассматриваемого файла.
Получите копию Обозреватель процессов Запустите. Нажмите Ctrl + F и введите имя файла. Он покажет вам список процессов, обращающихся к нему.
Возможно ли, что файл находится в процессе закрытия (т.е. SQLLite не выпускает его немедленно)?
Вы можете попробовать закрыть базу данных в цикле задержки (может быть, одна секунда между каждой попыткой) и выбросить исключение только после нескольких (5 или около того) итераций через цикл.
Звучит довольно взломанно. Я бы подумал, что явный вызов connection.Close () заставит SQLite освободить файл (если он действительно хранится в SQLite).
дешевый хакер! Это то, как вы так быстро справляетесь с делами?
Хе-хе ... Я не говорил, что это будет окончательное решение ... Это поможет вам диагностировать проблему, хотя :) У вас нет прямого контроля (кроме вызова Close ()) над тем, что SQLLite делает с файлом .
Что вы используете для открытия своей базы данных? А вы используете коннектор ADO 2.0 от здесь. Если у меня есть приложение, которое его использует, и я могу подключиться к нему несколько раз (закрыть / открыть). Если вы не используете этот разъем, вы можете попробовать. Что возвращает ваш метод Connect ()?
Connect () прямо здесь в моем вопросе - он возвращает DbConnection, который на самом деле является SQLite DbConnection.
(И да, я использую System.Data.SQLite).
Я знаю, что вопрос в Connect, возвращает ли он действительное соединение? Вы можете делать свои запросы? Вы говорите, что все терпит неудачу, а затем идите на разборку, что будет, если не удастся?
Да, он возвращает действительное соединение, поскольку доступ к базе данных работает нормально.
«Ты говоришь, что все терпит неудачу, а потом разбирайся» - я этого не говорил. Тесты работают отлично, единственное, что не удается - это единственная строка в методе [TearDown]. Если я закомментирую эту строку, все мои тесты пройдут!
Спасибо за указание на это!
Проблема не связана с SQLite, а скорее с управлением памятью в целом. После запуска теста объекты, указывающие на файлы, больше не имеют области видимости, но они все еще существуют в памяти.
Следовательно, все еще есть ссылки на файлы, и вы не можете их удалить / переместить.
Я знаю, что этот ответ опоздал более чем на год, но для тех, кто будет читать в будущем ...
У меня была проблема, аналогичная вашей - попытка удалить тестовые базы данных между тестами не удалась из-за того, что файл базы данных SQLite остался открытым. Я отследил проблему в своем коде до того, что объект SQLiteDataReader не был явно закрыт.
SQLiteDataReader dr = cmd_.ExecuteReader();
while (dr.Read())
{
// use the DataReader results
}
dr.Close(); // <-- necessary for database resources to be released
Вызов статического метода
SqliteConnection.ClearAllPools ()
После этого вызова файл базы данных разблокируется, и вы можете удалить файл в [TearDown].
Спасибо за опубликованный ответ внизу. Я часами копал точно такой же случай и
GC.Collect ();
GC.WaitForPendingFinalizers ();
сделал свое дело.
Ваше решение сработало для меня с добавлением: GC.WaitForPendingFinalizers ();