python मुड़ की एडबैपी यूटैट्स के भीतर से डेटा को पुनर्प्राप्त करने में असफल क्यों है?




unit-testing sqlite3 (2)

अवलोकन

प्रसंग

मैं कुछ उच्च-क्रम तर्कों के लिए यूनिट परीक्षण लिख रहा हूं जो एक SQLite3 डेटाबेस को लिखने पर निर्भर करता है इसके लिए मैं twisted.trial.unittest और twisted.enterprise.adbapi.ConnectionPool का उपयोग कर रहा हूँ।

समस्या का विवरण

मैं उसमें एक सतत एसक्यूएलटी 3 डाटाबेस और स्टोर डेटा बनाने में सक्षम हूं। SQLitebrowser का उपयोग करके , मैं यह सत्यापित करने में सक्षम हूं कि डेटा अपेक्षित रूप से कायम रहा है

मुद्दा यह है कि teaConnectionPool.run* (जैसे: runQuery ) को कॉल परिणाम के खाली सेट लौटाते हैं, लेकिन केवल जब एक runQuery भीतर से कॉल किया जाता है

नोट्स और महत्वपूर्ण विवरण

जो समस्या मैं अनुभव कर रहा हूं वह केवल मुड़ के trial ढांचे के भीतर होती है डीबगिंग पर मेरा पहला प्रयास था युनाइटेड टेस्ट के डाटाबेस कोड को निकालने और इसे एक स्वतंत्र परीक्षण / डीबग स्क्रिप्ट में डाल दिया। स्क्रिप्ट के अनुसार अपेक्षित काम करता है, जबकि यूनिट परीक्षण कोड नहीं देता (नीचे दिए गए उदाहरण देखें)

मामला 1: दुर्व्यवहार इकाई परीक्षण

init.sql

यह डेटाबेस को आरंभ करने के लिए उपयोग की गई स्क्रिप्ट है इस फ़ाइल से ग्रस्त कोई (स्पष्ट) त्रुटियां नहीं हैं

CREATE TABLE ajxp_changes ( seq INTEGER PRIMARY KEY AUTOINCREMENT, node_id NUMERIC, type TEXT, source TEXT, target TEXT, deleted_md5 TEXT );
CREATE TABLE ajxp_index ( node_id INTEGER PRIMARY KEY AUTOINCREMENT, node_path TEXT, bytesize NUMERIC, md5 TEXT, mtime NUMERIC, stat_result BLOB);
CREATE TABLE ajxp_last_buffer ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT, location TEXT, source TEXT, target TEXT );
CREATE TABLE ajxp_node_status ("node_id" INTEGER PRIMARY KEY  NOT NULL , "status" TEXT NOT NULL  DEFAULT 'NEW', "detail" TEXT);
CREATE TABLE events (id INTEGER PRIMARY KEY AUTOINCREMENT, type text, message text, source text, target text, action text, status text, date text);

CREATE TRIGGER LOG_DELETE AFTER DELETE ON ajxp_index BEGIN INSERT INTO ajxp_changes (node_id,source,target,type,deleted_md5) VALUES (old.node_id, old.node_path, "NULL", "delete", old.md5); END;
CREATE TRIGGER LOG_INSERT AFTER INSERT ON ajxp_index BEGIN INSERT INTO ajxp_changes (node_id,source,target,type) VALUES (new.node_id, "NULL", new.node_path, "create"); END;
CREATE TRIGGER LOG_UPDATE_CONTENT AFTER UPDATE ON "ajxp_index" FOR EACH ROW BEGIN INSERT INTO "ajxp_changes" (node_id,source,target,type) VALUES (new.node_id, old.node_path, new.node_path, CASE WHEN old.node_path = new.node_path THEN "content" ELSE "path" END);END;
CREATE TRIGGER STATUS_DELETE AFTER DELETE ON "ajxp_index" BEGIN DELETE FROM ajxp_node_status WHERE node_id=old.node_id; END;
CREATE TRIGGER STATUS_INSERT AFTER INSERT ON "ajxp_index" BEGIN INSERT INTO ajxp_node_status (node_id) VALUES (new.node_id); END;

CREATE INDEX changes_node_id ON ajxp_changes( node_id );
CREATE INDEX changes_type ON ajxp_changes( type );
CREATE INDEX changes_node_source ON ajxp_changes( source );
CREATE INDEX index_node_id ON ajxp_index( node_id );
CREATE INDEX index_node_path ON ajxp_index( node_path );
CREATE INDEX index_bytesize ON ajxp_index( bytesize );
CREATE INDEX index_md5 ON ajxp_index( md5 );
CREATE INDEX node_status_status ON ajxp_node_status( status );

test_sqlite.py

यह यूनिट टेस्ट क्लास है जो अप्रत्याशित रूप से विफल रहता है। TestStateManagement.test_db_clean पास, इंगित करता है कि तालिकाओं को ठीक से बनाया गया था TestStateManagement.test_inode_create विफल, repoitng कि शून्य परिणाम पुनर्प्राप्त किए गए थे।

import os.path as osp

from twisted.internet import defer
from twisted.enterprise import adbapi

import sqlengine # see below

class TestStateManagement(TestCase):

    def setUp(self):
        self.meta = mkdtemp()

        self.db = adbapi.ConnectionPool(
            "sqlite3", osp.join(self.meta, "db.sqlite"), check_same_thread=False,
        )
        self.stateman = sqlengine.StateManager(self.db)

        with open("init.sql") as f:
            script = f.read()

        self.d = self.db.runInteraction(lambda c, s: c.executescript(s), script)

    def tearDown(self):
        self.db.close()
        del self.db
        del self.stateman
        del self.d

        rmtree(self.meta)

    @defer.inlineCallbacks
    def test_db_clean(self):
        """Canary test to ensure that the db is initialized in a blank state"""

        yield self.d  # wait for db to be initialized

        q = "SELECT name FROM sqlite_master WHERE type='table' AND name=?;"
        for table in ("ajxp_index", "ajxp_changes"):
            res = yield self.db.runQuery(q, (table,))
            self.assertTrue(
                len(res) == 1,
                "table {0} does not exist".format(table)
         )

    @defer.inlineCallbacks
    def test_inode_create_file(self):
        yield self.d

        path = osp.join(self.ws, "test.txt")
        with open(path, "wt") as f:
            pass

        inode = mk_dummy_inode(path)
        yield self.stateman.create(inode, directory=False)

        entry = yield self.db.runQuery("SELECT * FROM ajxp_index")
        emsg = "got {0} results, expected 1.  Are canary tests failing?"
        lentry = len(entry)
        self.assertTrue(lentry == 1, emsg.format(lentry))

sqlengine.py

ये उपर्युक्त इकाई परीक्षणों द्वारा परीक्षण किए जा रहे कलाकृतियों हैं।

def values_as_tuple(d, *param):
    """Return the values for each key in `param` as a tuple"""
    return tuple(map(d.get, param))


class StateManager:
    """Manages the SQLite database's state, ensuring that it reflects the state
    of the filesystem.
    """

    log = Logger()

    def __init__(self, db):
        self._db = db

    def create(self, inode, directory=False):
        params = values_as_tuple(
            inode, "node_path", "bytesize", "md5", "mtime", "stat_result"
        )

        directive = (
            "INSERT INTO ajxp_index (node_path,bytesize,md5,mtime,stat_result) "
            "VALUES (?,?,?,?,?);"
        )

        return self._db.runOperation(directive, params)

मामले 2: बग twisted.trial बाहर गायब हो जाता है

#! /usr/bin/env python

import os.path as osp
from tempfile import mkdtemp

from twisted.enterprise import adbapi
from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks

INIT_FILE = "example.sql"


def values_as_tuple(d, *param):
    """Return the values for each key in `param` as a tuple"""
    return tuple(map(d.get, param))


def create(db, inode):
    params = values_as_tuple(
        inode, "node_path", "bytesize", "md5", "mtime", "stat_result"
    )

    directive = (
        "INSERT INTO ajxp_index (node_path,bytesize,md5,mtime,stat_result) "
        "VALUES (?,?,?,?,?);"
    )

    return db.runOperation(directive, params)


def init_database(db):
    with open(INIT_FILE) as f:
        script = f.read()

    return db.runInteraction(lambda c, s: c.executescript(s), script)


@react
@inlineCallbacks
def main(reactor):
    meta = mkdtemp()
    db = adbapi.ConnectionPool(
        "sqlite3", osp.join(meta, "db.sqlite"), check_same_thread=False,
    )

    yield init_database(db)

    # Let's make sure the tables were created as expected and that we're
    # starting from a blank slate
    res = yield db.runQuery("SELECT * FROM ajxp_index LIMIT 1")
    assert not res, "database is not empty [ajxp_index]"

    res = yield db.runQuery("SELECT * FROM ajxp_changes LIMIT 1")
    assert not res, "database is not empty [ajxp_changes]"

    # The details of this are not important.  Suffice to say they (should)
    # conform to the DB schema for ajxp_index.
    test_data = {
        "node_path": "/this/is/some/arbitrary/path.ext",
        "bytesize": 0,
        "mtime": 179273.0,
        "stat_result": b"this simulates a blob of raw binary data",
        "md5": "d41d8cd98f00b204e9800998ecf8427e",  # arbitrary
    }

    # store the test data in the ajxp_index table
    yield create(db, test_data)

    # test if the entry exists in the db
    entry = yield db.runQuery("SELECT * FROM ajxp_index")
    assert len(entry) == 1, "got {0} results, expected 1".format(len(entry))

    print("OK")

अंतिम शब्द

दोबारा, sqlitebrowser के साथ जाँच करने पर, ऐसा लगता है कि डेटा को db.sqlite लिखा जा रहा है, इसलिए यह पुनर्प्राप्ति समस्या की तरह दिखता है। यहां से, मैं एक तरह से स्टम्प्ड हूं ... किसी भी विचार?

संपादित करें

यह कोड एक inode उत्पादन करेगा जो कि परीक्षण के लिए इस्तेमाल किया जा सकता है।

def mk_dummy_inode(path, isdir=False):
return {
    "node_path": path,
    "bytesize": osp.getsize(path),
    "mtime": osp.getmtime(path),
    "stat_result": dumps(stat(path), protocol=4),
    "md5": "directory" if isdir else "d41d8cd98f00b204e9800998ecf8427e",
}

https://code.i-harness.com


ठीक है, यह पता चला है कि यह एक मुश्किल एक का थोड़ा सा है। अलगाव में परीक्षण चलाना (जैसा कि इस प्रश्न पर पोस्ट किया गया था) ऐसा बना देता है कि बग केवल कम ही होता है हालांकि, जब एक पूरे परीक्षण सूट के संदर्भ में चल रहा है, यह लगभग 100% समय में विफल रहता है।

डीबी को लिखने के बाद और डीबी से पढ़ने से पहले, और इस मुद्दे को हल करने के लिए, मैं yield task.deferLater(reactor, .00001, lambda: None) जोड़ा। yield task.deferLater(reactor, .00001, lambda: None)

वहां से, मुझे संदेह हुआ कि यह कनेक्शन पूल और स्क्लिट की सीमित संगामिति-सहिष्णुता से एक दौड़ की स्थिति हो सकती है। मैंने ConnectionPool से 1 लिए cb_min और cb_max पैरामीटर सेट करने की कोशिश की, और इसने भी समस्या हल की।

संक्षेप में: ऐसा लगता है कि SQLite कई कनेक्शनों के साथ बहुत अच्छी तरह से नहीं खेलता है, और यह कि उचित तय करने के लिए संभवतया संभवत: संगामिति से बचने के लिए है


यदि आप अपने सेट अप फ़ंक्शन पर एक नज़र डालते हैं, तो आप self.db.runInteraction(...) लौट रहे हैं, जो एक आस्थगित रिटर्न देता है जैसा आपने नोट किया है, आप मानते हैं कि यह आस्थगित करने के लिए प्रतीक्षा करता है हालांकि यह मामला नहीं है और यह एक जाल है जो सबसे अधिक शिकार करने के लिए (मेरे शामिल है)। मैं आपके साथ ईमानदार रहूँगा, इस तरह की परिस्थितियों के लिए, विशेष रूप से यूनिट परीक्षणों के लिए, मैं डेटाबेस को प्रारंभ करने के लिए TestCase क्लास के बाहर तुल्यकालिक कोड निष्पादित करता हूं। उदाहरण के लिए:

def init_db():
    import sqlite3
    conn = sqlite3.connect('db.sqlite')
    c = conn.cursor()
    with open("init.sql") as f:
        c.executescript(f.read())

init_db()     # call outside test case


class TestStateManagement(TestCase):
    """
    My test cases
    """

वैकल्पिक रूप से, आप सेटअप को सजाने और yield runOperation(...) कर सकते हैं लेकिन कुछ मुझे बताता है कि यह काम नहीं करेगा ... किसी भी मामले में, यह आश्चर्यजनक है कि कोई भी त्रुटि नहीं उठाई गई है।

पुनश्च

मैं थोड़ी देर के लिए इस प्रश्न की ओर इशारा कर रहा हूं और आज के दिनों में मेरे सिर के पीछे है। आखिरकार 1:00 बजे मेरे लिए यह एक संभावित कारण है। हालांकि, मैं वास्तव में इसका परीक्षण करने के लिए बहुत थक / आलसी हूं: डी, ​​लेकिन यह एक बहुत अच्छा कर्कश है मैं इस प्रश्न में आपके विस्तार के स्तर पर आपको सराहना करूंगा।





python-db-api