Ошибка NUnit [TearDown] - какой процесс обращается к моим файлам?

Окончательное редактирование: Нашел решение проблемы (внизу вопроса).

У меня проблема с 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 должна быть ошибка.

Ваше решение сработало для меня с добавлением: GC.WaitForPendingFinalizers ();

Tom Robinson 10.08.2012 13:04

Ваш метод сбора мусора у меня тоже сработал, спасибо.

Mert Akcakaya 08.04.2013 15:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
19
2
4 091
11

Ответы 11

попробуйте вызвать Close на dbconnection

убедитесь, что процесс sqllite завершен

вы можете увидеть, какой процесс заблокировал ваш файл с помощью утилиты Unlocker (бесплатно)

это может быть "известная" проблема с SqlLite; подшучивание на форуме предлагает закрыть соединение и удалить команду, а также предполагает, что это будет исправлено в будущей версии (поскольку такое поведение не согласуется с другими поставщиками ADO)

Его предложение using должно вызывать Dispose, который вызывает Close. Но все же попробовать стоит!

TheSoftwareJedi 17.12.2008 16:57

Вызов Close () не помогает - я получаю точно такой же результат.

Stewart Johnson 17.12.2008 17:01

@ [Стюарт Джонсон]: см. Правки; Unlocker сообщит вам, у кого открыт ваш файл, вероятно, это SQLLite ...

Steven A. Lowe 17.12.2008 20:37

Есть ли способ заставить SQLite освободить файл? Я пробовал Close (), и DbConnection выходит за рамки, я не уверен, что еще делать.

Stewart Johnson 18.12.2008 03:11

@ [Стюарт Джонсон]: вы проверили с помощью Unlocker, что Sqlite все еще держит файл открытым?

Steven A. Lowe 18.12.2008 03:50

@ [Стюарт Джонсон]: это может быть известная проблема с / Sqlite, см. Правки

Steven A. Lowe 18.12.2008 04:01

@Steven: похоже, люди в этом потоке не вызывают Dispose () и не получают проблем с блокировкой файлов. Я являюсь вызываю Dispose () - в закрывающей скобке моего using (). Если я не правильно читаю эту ветку?

Stewart Johnson 18.12.2008 04:55

@ [Стюарт Джонсон]: как я читал поток, вам также необходимо вызвать Dispose для объекта Command, используя соединение, из-за ошибки в поставщике SqlLite

Steven A. Lowe 18.12.2008 04:59

Хорошо, я попробую (когда я дома - кода нет на работе). Спасибо.

Stewart Johnson 18.12.2008 05:04

Итак, я обернул все свои DbCommands в блоки using (), так что они были Dispose () d непосредственно перед подключением Dispose () d. Это не имело значения - та же проблема. :-(

Stewart Johnson 19.12.2008 08:22

@ [Стюарт Джонсон]: Тогда я называю "баг" в SqLite. Вы можете попробовать воспользоваться списком рассылки разработчиков sqlite.org:8080/cgi-bin/mailman/listinfo/sqlite-dev или заплатить за поддержку, или, если это открытый исходный код, отлаживать @ # $% самостоятельно. Извините, я ничем не могу помочь, но мой гугл-фу исчерпан!

Steven A. Lowe 19.12.2008 08:54

Я нашел решение! Добавление GC.Collect () непосредственно перед строкой File.Delete в моем методе [TearDown] избавляет от исключений. Когда я отлаживаю, больше нет ссылок на соединение, поэтому я не уверен, как работает GC.Collect, но это так.

Stewart Johnson 19.12.2008 10:41

@ [Стюарт Джонсон]: Я рад, что это сработало для вас, но я думаю, что это в значительной степени доказывает, что это ошибка в SQLite - удерживать блокировку файла до тех пор, пока не будет завершен объект, на который нет ссылки, - это бааааад!

Steven A. Lowe 19.12.2008 17:55

Разборка выполняется после каждого теста.

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] будет иметь значение?

Stewart Johnson 17.12.2008 17:16

«Возможно, в одном тесте используется файл, который вы пытаетесь удалить в другом тесте». Невозможно - см. Мой последний комментарий в моем вопросе: я получаю тот же результат, даже когда я запускаю каждый тест отдельно.

Stewart Johnson 17.12.2008 17:17

Вы не предоставляете достаточно информации о том, что делаете. По крайней мере, попробуйте то, что мы вам предлагаем. Вы пытались очистить весь тестовый файл с помощью только одного теста и попробовать его? Если это работает, значит, у вас несколько тестовых задач (они обращаются друг к другу). Плохая деталь вопроса, неправильный ответ, извините.

Patrick Desjardins 17.12.2008 18:41

Да, я пробовал запустить один тест и [TearDown] и [TestFixtureTearDown] удалили файл, и у меня возникла точно такая же проблема. Думаю, в моем вопросе много деталей. Что еще нужно знать?

Stewart Johnson 18.12.2008 03:04

Во-первых, успокойся. Если бы у вас было столько хороших деталей, проблема уже была бы решена. Во-вторых, голосование "против" не решит вашу проблему. Вы только что потеряли того, кто был готов вам помочь.

Patrick Desjardins 18.12.2008 04:08

Во-вторых, подробнее о том, работает ли это в вашей основной программе? Что за запрос? Вы говорите, что он потерпел неудачу, а затем отправился на разборку, что произойдет при успешном запросе? и т.п.

Patrick Desjardins 18.12.2008 04:11

@ Даок - Я не совсем понимаю, о чем ты говоришь. Я спокоен. Вы говорите мне, что в вопросе недостаточно подробностей, но вы не говорите мне, что еще вам нужно. Удалить () не удается независимо от того, какой запрос выполняется. Как вы думаете, почему это я проголосовал против вас?

Stewart Johnson 18.12.2008 04:47

@Daok: «Ты говоришь, что он провалился, а потом разбирайся» - я этого не говорил. Все работает Кроме той единственной строчкой в ​​[TearDown].

Stewart Johnson 18.12.2008 05:08

Мне нужно увидеть логику создания вашего имени файла, но возможно, что вы открываете файл для его создания, но не закрываете его после его создания. Я думаю, что если вы используете System.IO.Path.GetTempFileName (), он просто создаст файл и вернет вам имя файла с закрытым файлом. Если вы генерируете собственное случайное имя и используете File.Open, вам нужно будет убедиться, что он закрыт после этого.

С другой стороны, я настоятельно рекомендую следовать стратегии имитации для абстрагирования базы данных, а не чтения / записи из реальной базы данных в ваших модульных тестах. Суть модульных тестов в том, что они должны быть очень быстрыми, а файловый ввод-вывод действительно их замедлит.

Логика создания файла уже есть - в вызове connection.Open (). SQLite создает для меня файл.

Stewart Johnson 18.12.2008 03:05

У вас работает антивирус? Некоторые антивирусные продукты сканируют файл, когда видят, что он закрывается. Если в программном обеспечении AV файл все еще открыт, когда вы приходите, чтобы удалить его, вы увидите эту проблему. Вы можете повторить попытку удаления с небольшой задержкой.

Я также видел, как это происходило с поисковыми индексаторами.

Неа. Ни AV, ни поисковых индексаторов.

Stewart Johnson 18.12.2008 03:03

Первым шагом будет определение того, кто держит дескриптор рассматриваемого файла.

Получите копию Обозреватель процессов Запустите. Нажмите Ctrl + F и введите имя файла. Он покажет вам список процессов, обращающихся к нему.

Возможно ли, что файл находится в процессе закрытия (т.е. SQLLite не выпускает его немедленно)?

Вы можете попробовать закрыть базу данных в цикле задержки (может быть, одна секунда между каждой попыткой) и выбросить исключение только после нескольких (5 или около того) итераций через цикл.

Звучит довольно взломанно. Я бы подумал, что явный вызов connection.Close () заставит SQLite освободить файл (если он действительно хранится в SQLite).

Stewart Johnson 18.12.2008 03:06

дешевый хакер! Это то, как вы так быстро справляетесь с делами?

Steven A. Lowe 18.12.2008 04:01

Хе-хе ... Я не говорил, что это будет окончательное решение ... Это поможет вам диагностировать проблему, хотя :) У вас нет прямого контроля (кроме вызова Close ()) над тем, что SQLLite делает с файлом .

Andrew Rollings 18.12.2008 17:20

Что вы используете для открытия своей базы данных? А вы используете коннектор ADO 2.0 от здесь. Если у меня есть приложение, которое его использует, и я могу подключиться к нему несколько раз (закрыть / открыть). Если вы не используете этот разъем, вы можете попробовать. Что возвращает ваш метод Connect ()?

Connect () прямо здесь в моем вопросе - он возвращает DbConnection, который на самом деле является SQLite DbConnection.

Stewart Johnson 18.12.2008 03:08

(И да, я использую System.Data.SQLite).

Stewart Johnson 18.12.2008 03:16

Я знаю, что вопрос в Connect, возвращает ли он действительное соединение? Вы можете делать свои запросы? Вы говорите, что все терпит неудачу, а затем идите на разборку, что будет, если не удастся?

Patrick Desjardins 18.12.2008 04:10

Да, он возвращает действительное соединение, поскольку доступ к базе данных работает нормально.

Stewart Johnson 18.12.2008 04:51

«Ты говоришь, что все терпит неудачу, а потом разбирайся» - я этого не говорил. Тесты работают отлично, единственное, что не удается - это единственная строка в методе [TearDown]. Если я закомментирую эту строку, все мои тесты пройдут!

Stewart Johnson 18.12.2008 04:52

Спасибо за указание на это!

Проблема не связана с 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 ();

сделал свое дело.

Другие вопросы по теме