lib-x17-fintech 2.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
- lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
- lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
- lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
- lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
- xfintech/__init__.py +0 -0
- xfintech/connect/__init__.py +18 -0
- xfintech/connect/artifact/__init__.py +5 -0
- xfintech/connect/artifact/artifact.py +168 -0
- xfintech/connect/artifact/tests/__init__.py +3 -0
- xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
- xfintech/connect/common/__init__.py +12 -0
- xfintech/connect/common/connect.py +49 -0
- xfintech/connect/common/connectref.py +119 -0
- xfintech/connect/common/error.py +62 -0
- xfintech/connect/common/tests/__init__.py +1 -0
- xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
- xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
- xfintech/connect/common/tests/test_class_errors_all.py +524 -0
- xfintech/connect/instance/__init__.py +7 -0
- xfintech/connect/instance/macos.py +121 -0
- xfintech/connect/instance/s3.py +176 -0
- xfintech/connect/instance/tests/__init__.py +1 -0
- xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
- xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
- xfintech/data/__init__.py +20 -0
- xfintech/data/common/__init__.py +15 -0
- xfintech/data/common/cache.py +186 -0
- xfintech/data/common/coolant.py +171 -0
- xfintech/data/common/metric.py +138 -0
- xfintech/data/common/paginate.py +132 -0
- xfintech/data/common/params.py +162 -0
- xfintech/data/common/retry.py +201 -0
- xfintech/data/common/tests/__init__.py +1 -0
- xfintech/data/common/tests/test_class_cache_all.py +681 -0
- xfintech/data/common/tests/test_class_coolant_all.py +534 -0
- xfintech/data/common/tests/test_class_metric_all.py +705 -0
- xfintech/data/common/tests/test_class_paginate_all.py +508 -0
- xfintech/data/common/tests/test_class_params_all.py +891 -0
- xfintech/data/common/tests/test_class_retry_all.py +714 -0
- xfintech/data/job/__init__.py +17 -0
- xfintech/data/job/errors.py +112 -0
- xfintech/data/job/house.py +156 -0
- xfintech/data/job/job.py +247 -0
- xfintech/data/job/joblike.py +47 -0
- xfintech/data/job/tests/__init__.py +1 -0
- xfintech/data/job/tests/test_class_errors_all.py +275 -0
- xfintech/data/job/tests/test_class_house_all.py +801 -0
- xfintech/data/job/tests/test_class_job_all.py +684 -0
- xfintech/data/job/tests/test_class_joblike_all.py +482 -0
- xfintech/data/relay/__init__.py +7 -0
- xfintech/data/relay/client.py +114 -0
- xfintech/data/relay/clientlike.py +45 -0
- xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
- xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
- xfintech/data/source/__init__.py +7 -0
- xfintech/data/source/baostock/__init__.py +21 -0
- xfintech/data/source/baostock/job/__init__.py +5 -0
- xfintech/data/source/baostock/job/job.py +217 -0
- xfintech/data/source/baostock/job/tests/__init__.py +0 -0
- xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
- xfintech/data/source/baostock/session/__init__.py +8 -0
- xfintech/data/source/baostock/session/relay.py +223 -0
- xfintech/data/source/baostock/session/session.py +241 -0
- xfintech/data/source/baostock/session/tests/__init__.py +0 -0
- xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
- xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
- xfintech/data/source/baostock/stock/__init__.py +0 -0
- xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
- xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
- xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
- xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
- xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
- xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
- xfintech/data/source/baostock/stock/stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/stock/stock.py +149 -0
- xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
- xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
- xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
- xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
- xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
- xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
- xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
- xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
- xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
- xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
- xfintech/data/source/tushare/__init__.py +61 -0
- xfintech/data/source/tushare/job/__init__.py +5 -0
- xfintech/data/source/tushare/job/job.py +257 -0
- xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
- xfintech/data/source/tushare/session/__init__.py +5 -0
- xfintech/data/source/tushare/session/relay.py +231 -0
- xfintech/data/source/tushare/session/session.py +239 -0
- xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
- xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
- xfintech/data/source/tushare/stock/__init__.py +55 -0
- xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
- xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
- xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
- xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
- xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
- xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
- xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
- xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
- xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
- xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
- xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
- xfintech/data/source/tushare/stock/company/__init__.py +19 -0
- xfintech/data/source/tushare/stock/company/company.py +188 -0
- xfintech/data/source/tushare/stock/company/constant.py +92 -0
- xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
- xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
- xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
- xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
- xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
- xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
- xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
- xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
- xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
- xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
- xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
- xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
- xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
- xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
- xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
- xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
- xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
- xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
- xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
- xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
- xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stock/constant.py +105 -0
- xfintech/data/source/tushare/stock/stock/stock.py +193 -0
- xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
- xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
- xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
- xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
- xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
- xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
- xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
- xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
- xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
- xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
- xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
- xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
- xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
- xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
- xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
- xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
- xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
- xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
- xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
- xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
- xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
- xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
- xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
- xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
- xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
- xfintech/fabric/__init__.py +18 -0
- xfintech/fabric/column/__init__.py +7 -0
- xfintech/fabric/column/info.py +202 -0
- xfintech/fabric/column/kind.py +102 -0
- xfintech/fabric/column/tests/__init__.py +0 -0
- xfintech/fabric/column/tests/test_class_info_all.py +207 -0
- xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
- xfintech/fabric/table/__init__.py +5 -0
- xfintech/fabric/table/info.py +263 -0
- xfintech/fabric/table/tests/__init__.py +0 -0
- xfintech/fabric/table/tests/test_class_info_all.py +547 -0
- xfintech/serde/__init__.py +35 -0
- xfintech/serde/common/__init__.py +9 -0
- xfintech/serde/common/dataformat.py +78 -0
- xfintech/serde/common/deserialiserlike.py +38 -0
- xfintech/serde/common/error.py +182 -0
- xfintech/serde/common/serialiserlike.py +38 -0
- xfintech/serde/common/tests/__init__.py +1 -0
- xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
- xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
- xfintech/serde/common/tests/test_class_errors_all.py +518 -0
- xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
- xfintech/serde/deserialiser/__init__.py +7 -0
- xfintech/serde/deserialiser/pandas.py +113 -0
- xfintech/serde/deserialiser/python.py +68 -0
- xfintech/serde/deserialiser/tests/__init__.py +1 -0
- xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
- xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
- xfintech/serde/serialiser/__init__.py +7 -0
- xfintech/serde/serialiser/pandas.py +116 -0
- xfintech/serde/serialiser/python.py +71 -0
- xfintech/serde/serialiser/tests/__init__.py +1 -0
- xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
- xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from xfintech.data.common.metric import Metric
|
|
7
|
+
|
|
8
|
+
# ==================== Metric Initialization Tests ====================
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_metric_init():
|
|
12
|
+
"""Test Metric initialization creates correct default attributes"""
|
|
13
|
+
metric = Metric()
|
|
14
|
+
|
|
15
|
+
assert metric.start_at is None
|
|
16
|
+
assert metric.finish_at is None
|
|
17
|
+
assert isinstance(metric.marks, dict)
|
|
18
|
+
assert len(metric.marks) == 0
|
|
19
|
+
assert isinstance(metric.errors, list)
|
|
20
|
+
assert len(metric.errors) == 0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_metric_init_multiple_instances():
|
|
24
|
+
"""Test that multiple Metric instances are independent"""
|
|
25
|
+
m1 = Metric()
|
|
26
|
+
m2 = Metric()
|
|
27
|
+
|
|
28
|
+
m1.start()
|
|
29
|
+
m1.mark("test")
|
|
30
|
+
|
|
31
|
+
assert m1.start_at is not None
|
|
32
|
+
assert m2.start_at is None
|
|
33
|
+
assert "test" in m1.marks
|
|
34
|
+
assert "test" not in m2.marks
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ==================== Start/Finish Tests ====================
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_metric_start():
|
|
41
|
+
"""Test start() records start timestamp"""
|
|
42
|
+
metric = Metric()
|
|
43
|
+
before = pd.Timestamp.now()
|
|
44
|
+
|
|
45
|
+
metric.start()
|
|
46
|
+
|
|
47
|
+
after = pd.Timestamp.now()
|
|
48
|
+
assert metric.start_at is not None
|
|
49
|
+
assert isinstance(metric.start_at, pd.Timestamp)
|
|
50
|
+
assert before <= metric.start_at <= after
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_metric_finish():
|
|
54
|
+
"""Test finish() records finish timestamp"""
|
|
55
|
+
metric = Metric()
|
|
56
|
+
metric.start()
|
|
57
|
+
time.sleep(0.01)
|
|
58
|
+
|
|
59
|
+
metric.finish()
|
|
60
|
+
|
|
61
|
+
assert metric.finish_at is not None
|
|
62
|
+
assert isinstance(metric.finish_at, pd.Timestamp)
|
|
63
|
+
assert metric.finish_at > metric.start_at
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_metric_start_multiple_times():
|
|
67
|
+
"""Test calling start() multiple times updates start_at"""
|
|
68
|
+
metric = Metric()
|
|
69
|
+
|
|
70
|
+
metric.start()
|
|
71
|
+
first_start = metric.start_at
|
|
72
|
+
time.sleep(0.01)
|
|
73
|
+
|
|
74
|
+
metric.start()
|
|
75
|
+
second_start = metric.start_at
|
|
76
|
+
|
|
77
|
+
assert second_start > first_start
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_metric_finish_without_start():
|
|
81
|
+
"""Test finish() can be called without start()"""
|
|
82
|
+
metric = Metric()
|
|
83
|
+
|
|
84
|
+
metric.finish()
|
|
85
|
+
|
|
86
|
+
assert metric.start_at is None
|
|
87
|
+
assert metric.finish_at is not None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ==================== Duration Property Tests ====================
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_metric_duration_not_started():
|
|
94
|
+
"""Test duration returns 0.0 when not started"""
|
|
95
|
+
metric = Metric()
|
|
96
|
+
|
|
97
|
+
assert metric.duration == 0.0
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_metric_duration_started_not_finished():
|
|
101
|
+
"""Test duration calculates time from start to now when not finished"""
|
|
102
|
+
metric = Metric()
|
|
103
|
+
metric.start()
|
|
104
|
+
time.sleep(0.05)
|
|
105
|
+
|
|
106
|
+
duration = metric.duration
|
|
107
|
+
|
|
108
|
+
assert duration >= 0.05
|
|
109
|
+
assert duration < 0.2 # reasonable upper bound
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_metric_duration_finished():
|
|
113
|
+
"""Test duration calculates time from start to finish"""
|
|
114
|
+
metric = Metric()
|
|
115
|
+
metric.start()
|
|
116
|
+
time.sleep(0.05)
|
|
117
|
+
metric.finish()
|
|
118
|
+
|
|
119
|
+
duration = metric.duration
|
|
120
|
+
|
|
121
|
+
assert duration >= 0.05
|
|
122
|
+
assert duration < 0.2
|
|
123
|
+
# Duration should be stable after finish
|
|
124
|
+
time.sleep(0.01)
|
|
125
|
+
assert metric.duration == duration
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_metric_duration_precision():
|
|
129
|
+
"""Test duration calculation precision"""
|
|
130
|
+
metric = Metric()
|
|
131
|
+
metric.start()
|
|
132
|
+
metric.finish()
|
|
133
|
+
|
|
134
|
+
duration = metric.duration
|
|
135
|
+
|
|
136
|
+
assert isinstance(duration, float)
|
|
137
|
+
assert duration >= 0
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ==================== Mark Tests ====================
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_metric_mark():
|
|
144
|
+
"""Test mark() records a named timestamp"""
|
|
145
|
+
metric = Metric()
|
|
146
|
+
before = pd.Timestamp.now()
|
|
147
|
+
|
|
148
|
+
metric.mark("checkpoint")
|
|
149
|
+
|
|
150
|
+
after = pd.Timestamp.now()
|
|
151
|
+
assert "checkpoint" in metric.marks
|
|
152
|
+
assert isinstance(metric.marks["checkpoint"], pd.Timestamp)
|
|
153
|
+
assert before <= metric.marks["checkpoint"] <= after
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_metric_mark_multiple():
|
|
157
|
+
"""Test marking multiple checkpoints"""
|
|
158
|
+
metric = Metric()
|
|
159
|
+
|
|
160
|
+
metric.mark("step1")
|
|
161
|
+
time.sleep(0.01)
|
|
162
|
+
metric.mark("step2")
|
|
163
|
+
time.sleep(0.01)
|
|
164
|
+
metric.mark("step3")
|
|
165
|
+
|
|
166
|
+
assert len(metric.marks) == 3
|
|
167
|
+
assert "step1" in metric.marks
|
|
168
|
+
assert "step2" in metric.marks
|
|
169
|
+
assert "step3" in metric.marks
|
|
170
|
+
assert metric.marks["step1"] < metric.marks["step2"] < metric.marks["step3"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_metric_mark_overwrite():
|
|
174
|
+
"""Test marking with same name overwrites previous mark"""
|
|
175
|
+
metric = Metric()
|
|
176
|
+
|
|
177
|
+
metric.mark("test")
|
|
178
|
+
first_mark = metric.marks["test"]
|
|
179
|
+
time.sleep(0.01)
|
|
180
|
+
|
|
181
|
+
metric.mark("test")
|
|
182
|
+
second_mark = metric.marks["test"]
|
|
183
|
+
|
|
184
|
+
assert len(metric.marks) == 1
|
|
185
|
+
assert second_mark > first_mark
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_metric_mark_without_start():
|
|
189
|
+
"""Test mark() works without calling start()"""
|
|
190
|
+
metric = Metric()
|
|
191
|
+
|
|
192
|
+
metric.mark("standalone")
|
|
193
|
+
|
|
194
|
+
assert "standalone" in metric.marks
|
|
195
|
+
assert metric.start_at is None
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# ==================== Reset Tests ====================
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_metric_reset():
|
|
202
|
+
"""Test reset() clears all state"""
|
|
203
|
+
metric = Metric()
|
|
204
|
+
metric.start()
|
|
205
|
+
metric.mark("test")
|
|
206
|
+
metric.errors = ["error1"]
|
|
207
|
+
metric.finish()
|
|
208
|
+
|
|
209
|
+
metric.reset()
|
|
210
|
+
|
|
211
|
+
assert metric.start_at is None
|
|
212
|
+
assert metric.finish_at is None
|
|
213
|
+
assert len(metric.marks) == 0
|
|
214
|
+
assert len(metric.errors) == 0
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def test_metric_reset_empty():
|
|
218
|
+
"""Test reset() on already empty metric"""
|
|
219
|
+
metric = Metric()
|
|
220
|
+
|
|
221
|
+
metric.reset()
|
|
222
|
+
|
|
223
|
+
assert metric.start_at is None
|
|
224
|
+
assert metric.finish_at is None
|
|
225
|
+
assert len(metric.marks) == 0
|
|
226
|
+
assert len(metric.errors) == 0
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def test_metric_reset_reusability():
|
|
230
|
+
"""Test metric can be reused after reset"""
|
|
231
|
+
metric = Metric()
|
|
232
|
+
metric.start()
|
|
233
|
+
metric.finish()
|
|
234
|
+
first_duration = metric.duration
|
|
235
|
+
|
|
236
|
+
metric.reset()
|
|
237
|
+
metric.start()
|
|
238
|
+
time.sleep(0.01)
|
|
239
|
+
metric.finish()
|
|
240
|
+
second_duration = metric.duration
|
|
241
|
+
|
|
242
|
+
assert first_duration != second_duration
|
|
243
|
+
assert metric.duration > 0
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ==================== ISO Format Tests ====================
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def test_metric_get_start_iso_none():
|
|
250
|
+
"""Test get_start_iso() returns None when not started"""
|
|
251
|
+
metric = Metric()
|
|
252
|
+
|
|
253
|
+
assert metric.get_start_iso() is None
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_metric_get_start_iso_format():
|
|
257
|
+
"""Test get_start_iso() returns valid ISO format string"""
|
|
258
|
+
metric = Metric()
|
|
259
|
+
metric.start()
|
|
260
|
+
|
|
261
|
+
iso_str = metric.get_start_iso()
|
|
262
|
+
|
|
263
|
+
assert iso_str is not None
|
|
264
|
+
assert isinstance(iso_str, str)
|
|
265
|
+
# Should be able to parse back
|
|
266
|
+
parsed = pd.Timestamp(iso_str)
|
|
267
|
+
assert isinstance(parsed, pd.Timestamp)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def test_metric_get_finish_iso_none():
|
|
271
|
+
"""Test get_finish_iso() returns None when not finished"""
|
|
272
|
+
metric = Metric()
|
|
273
|
+
|
|
274
|
+
assert metric.get_finish_iso() is None
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_metric_get_finish_iso_format():
|
|
278
|
+
"""Test get_finish_iso() returns valid ISO format string"""
|
|
279
|
+
metric = Metric()
|
|
280
|
+
metric.finish()
|
|
281
|
+
|
|
282
|
+
iso_str = metric.get_finish_iso()
|
|
283
|
+
|
|
284
|
+
assert iso_str is not None
|
|
285
|
+
assert isinstance(iso_str, str)
|
|
286
|
+
# Should be able to parse back
|
|
287
|
+
parsed = pd.Timestamp(iso_str)
|
|
288
|
+
assert isinstance(parsed, pd.Timestamp)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def test_metric_get_mark_iso_empty():
|
|
292
|
+
"""Test get_mark_iso() returns empty dict when no marks"""
|
|
293
|
+
metric = Metric()
|
|
294
|
+
|
|
295
|
+
marks_iso = metric.get_mark_iso()
|
|
296
|
+
|
|
297
|
+
assert isinstance(marks_iso, dict)
|
|
298
|
+
assert len(marks_iso) == 0
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def test_metric_get_mark_iso_format():
|
|
302
|
+
"""Test get_mark_iso() returns marks in ISO format"""
|
|
303
|
+
metric = Metric()
|
|
304
|
+
metric.mark("test1")
|
|
305
|
+
metric.mark("test2")
|
|
306
|
+
|
|
307
|
+
marks_iso = metric.get_mark_iso()
|
|
308
|
+
|
|
309
|
+
assert len(marks_iso) == 2
|
|
310
|
+
assert "test1" in marks_iso
|
|
311
|
+
assert "test2" in marks_iso
|
|
312
|
+
assert isinstance(marks_iso["test1"], str)
|
|
313
|
+
assert isinstance(marks_iso["test2"], str)
|
|
314
|
+
# Should be able to parse back
|
|
315
|
+
parsed1 = pd.Timestamp(marks_iso["test1"])
|
|
316
|
+
parsed2 = pd.Timestamp(marks_iso["test2"])
|
|
317
|
+
assert isinstance(parsed1, pd.Timestamp)
|
|
318
|
+
assert isinstance(parsed2, pd.Timestamp)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# ==================== Context Manager Tests ====================
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def test_metric_context_manager_success():
|
|
325
|
+
"""Test Metric as context manager with successful execution"""
|
|
326
|
+
with Metric() as metric:
|
|
327
|
+
assert metric.start_at is not None
|
|
328
|
+
time.sleep(0.01)
|
|
329
|
+
metric.mark("checkpoint")
|
|
330
|
+
|
|
331
|
+
assert metric.finish_at is not None
|
|
332
|
+
assert metric.duration > 0
|
|
333
|
+
assert "checkpoint" in metric.marks
|
|
334
|
+
assert len(metric.errors) == 0
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_metric_context_manager_returns_self():
|
|
338
|
+
"""Test context manager returns the metric instance"""
|
|
339
|
+
metric = Metric()
|
|
340
|
+
|
|
341
|
+
with metric as m:
|
|
342
|
+
assert m is metric
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def test_metric_context_manager_resets_on_enter():
|
|
346
|
+
"""Test context manager resets state on __enter__"""
|
|
347
|
+
metric = Metric()
|
|
348
|
+
metric.start()
|
|
349
|
+
metric.mark("old")
|
|
350
|
+
metric.errors = ["old_error"]
|
|
351
|
+
|
|
352
|
+
with metric as m:
|
|
353
|
+
assert m.start_at is not None
|
|
354
|
+
assert len(m.marks) == 0
|
|
355
|
+
assert len(m.errors) == 0
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def test_metric_context_manager_exception_capture():
|
|
359
|
+
"""Test context manager captures exceptions"""
|
|
360
|
+
try:
|
|
361
|
+
with Metric() as metric:
|
|
362
|
+
raise ValueError("Test exception")
|
|
363
|
+
except ValueError:
|
|
364
|
+
pass # Exception propagated but captured in metric
|
|
365
|
+
|
|
366
|
+
# Exception should be captured in errors
|
|
367
|
+
assert len(metric.errors) > 0
|
|
368
|
+
assert any("ValueError" in error for error in metric.errors)
|
|
369
|
+
assert any("Test exception" in error for error in metric.errors)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def test_metric_context_manager_exception_propagation():
|
|
373
|
+
"""Test context manager doesn't suppress exceptions"""
|
|
374
|
+
with pytest.raises(ValueError, match="Test error"):
|
|
375
|
+
with Metric():
|
|
376
|
+
raise ValueError("Test error")
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def test_metric_context_manager_exception_traceback():
|
|
380
|
+
"""Test context manager captures full traceback"""
|
|
381
|
+
try:
|
|
382
|
+
with Metric() as metric:
|
|
383
|
+
|
|
384
|
+
def inner_function():
|
|
385
|
+
raise RuntimeError("Inner error")
|
|
386
|
+
|
|
387
|
+
inner_function()
|
|
388
|
+
except RuntimeError:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
assert len(metric.errors) > 0
|
|
392
|
+
# Should contain traceback information
|
|
393
|
+
full_traceback = "\n".join(metric.errors)
|
|
394
|
+
assert "RuntimeError" in full_traceback
|
|
395
|
+
assert "Inner error" in full_traceback
|
|
396
|
+
assert "inner_function" in full_traceback
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
def test_metric_context_manager_no_exception():
|
|
400
|
+
"""Test context manager with no exception"""
|
|
401
|
+
with Metric() as metric:
|
|
402
|
+
time.sleep(0.01)
|
|
403
|
+
|
|
404
|
+
assert len(metric.errors) == 0
|
|
405
|
+
assert metric.finish_at is not None
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
# ==================== Describe Method Tests ====================
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_metric_describe_empty():
|
|
412
|
+
"""Test describe() with no data"""
|
|
413
|
+
metric = Metric()
|
|
414
|
+
|
|
415
|
+
result = metric.describe()
|
|
416
|
+
|
|
417
|
+
assert isinstance(result, dict)
|
|
418
|
+
assert "duration" in result
|
|
419
|
+
assert result["duration"] == 0.0
|
|
420
|
+
# Should not include None fields
|
|
421
|
+
assert "started_at" not in result
|
|
422
|
+
assert "finished_at" not in result
|
|
423
|
+
assert "errors" not in result
|
|
424
|
+
assert "marks" not in result
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def test_metric_describe_started():
|
|
428
|
+
"""Test describe() with only start"""
|
|
429
|
+
metric = Metric()
|
|
430
|
+
metric.start()
|
|
431
|
+
|
|
432
|
+
result = metric.describe()
|
|
433
|
+
|
|
434
|
+
assert "started_at" in result
|
|
435
|
+
assert "duration" in result
|
|
436
|
+
assert result["duration"] > 0
|
|
437
|
+
assert "finished_at" not in result
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def test_metric_describe_finished():
|
|
441
|
+
"""Test describe() with start and finish"""
|
|
442
|
+
metric = Metric()
|
|
443
|
+
metric.start()
|
|
444
|
+
time.sleep(0.01)
|
|
445
|
+
metric.finish()
|
|
446
|
+
|
|
447
|
+
result = metric.describe()
|
|
448
|
+
|
|
449
|
+
assert "started_at" in result
|
|
450
|
+
assert "finished_at" in result
|
|
451
|
+
assert "duration" in result
|
|
452
|
+
assert result["duration"] > 0
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def test_metric_describe_with_marks():
|
|
456
|
+
"""Test describe() includes marks"""
|
|
457
|
+
metric = Metric()
|
|
458
|
+
metric.start()
|
|
459
|
+
metric.mark("checkpoint")
|
|
460
|
+
metric.finish()
|
|
461
|
+
|
|
462
|
+
result = metric.describe()
|
|
463
|
+
|
|
464
|
+
assert "marks" in result
|
|
465
|
+
assert isinstance(result["marks"], dict)
|
|
466
|
+
assert "checkpoint" in result["marks"]
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def test_metric_describe_with_errors():
|
|
470
|
+
"""Test describe() includes errors"""
|
|
471
|
+
metric = Metric()
|
|
472
|
+
try:
|
|
473
|
+
with metric:
|
|
474
|
+
raise ValueError("Test")
|
|
475
|
+
except ValueError:
|
|
476
|
+
pass
|
|
477
|
+
|
|
478
|
+
result = metric.describe()
|
|
479
|
+
|
|
480
|
+
assert "errors" in result
|
|
481
|
+
assert isinstance(result["errors"], list)
|
|
482
|
+
assert len(result["errors"]) > 0
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def test_metric_describe_complete():
|
|
486
|
+
"""Test describe() with all fields populated"""
|
|
487
|
+
metric = Metric()
|
|
488
|
+
try:
|
|
489
|
+
with metric:
|
|
490
|
+
metric.mark("step1")
|
|
491
|
+
raise Exception("test error")
|
|
492
|
+
except Exception:
|
|
493
|
+
pass
|
|
494
|
+
|
|
495
|
+
result = metric.describe()
|
|
496
|
+
|
|
497
|
+
assert "started_at" in result
|
|
498
|
+
assert "finished_at" in result
|
|
499
|
+
assert "duration" in result
|
|
500
|
+
assert "marks" in result
|
|
501
|
+
assert "errors" in result
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
# ==================== To Dict Method Tests ====================
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def test_metric_to_dict_empty():
|
|
508
|
+
"""Test to_dict() with no data includes all fields"""
|
|
509
|
+
metric = Metric()
|
|
510
|
+
|
|
511
|
+
result = metric.to_dict()
|
|
512
|
+
|
|
513
|
+
assert isinstance(result, dict)
|
|
514
|
+
assert "started_at" in result
|
|
515
|
+
assert "finished_at" in result
|
|
516
|
+
assert "duration" in result
|
|
517
|
+
assert "errors" in result
|
|
518
|
+
assert "marks" in result
|
|
519
|
+
# Values should be None/empty but keys present
|
|
520
|
+
assert result["started_at"] is None
|
|
521
|
+
assert result["finished_at"] is None
|
|
522
|
+
assert result["duration"] == 0.0
|
|
523
|
+
assert result["errors"] == []
|
|
524
|
+
assert result["marks"] == {}
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def test_metric_to_dict_with_data():
|
|
528
|
+
"""Test to_dict() returns complete dictionary"""
|
|
529
|
+
metric = Metric()
|
|
530
|
+
metric.start()
|
|
531
|
+
metric.mark("test")
|
|
532
|
+
time.sleep(0.01)
|
|
533
|
+
metric.finish()
|
|
534
|
+
|
|
535
|
+
result = metric.to_dict()
|
|
536
|
+
|
|
537
|
+
assert result["started_at"] is not None
|
|
538
|
+
assert result["finished_at"] is not None
|
|
539
|
+
assert result["duration"] > 0
|
|
540
|
+
assert len(result["marks"]) == 1
|
|
541
|
+
assert "test" in result["marks"]
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def test_metric_to_dict_vs_describe():
|
|
545
|
+
"""Test to_dict() includes None values while describe() omits them"""
|
|
546
|
+
metric = Metric()
|
|
547
|
+
|
|
548
|
+
to_dict_result = metric.to_dict()
|
|
549
|
+
describe_result = metric.describe()
|
|
550
|
+
|
|
551
|
+
# to_dict should have None values
|
|
552
|
+
assert "started_at" in to_dict_result
|
|
553
|
+
assert to_dict_result["started_at"] is None
|
|
554
|
+
|
|
555
|
+
# describe should not have None values
|
|
556
|
+
assert "started_at" not in describe_result
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def test_metric_to_dict_structure():
|
|
560
|
+
"""Test to_dict() returns expected structure"""
|
|
561
|
+
metric = Metric()
|
|
562
|
+
metric.mark("test")
|
|
563
|
+
|
|
564
|
+
result = metric.to_dict()
|
|
565
|
+
|
|
566
|
+
# Result should have correct structure
|
|
567
|
+
assert "marks" in result
|
|
568
|
+
assert "errors" in result
|
|
569
|
+
assert "test" in result["marks"]
|
|
570
|
+
assert isinstance(result["errors"], list)
|
|
571
|
+
|
|
572
|
+
# Note: to_dict returns references to internal state, not deep copies
|
|
573
|
+
# This is a known behavior where modifying result affects metric
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
# ==================== Integration Tests ====================
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def test_metric_full_workflow():
|
|
580
|
+
"""Test complete metric workflow"""
|
|
581
|
+
metric = Metric()
|
|
582
|
+
|
|
583
|
+
# Start timing
|
|
584
|
+
metric.start()
|
|
585
|
+
assert metric.start_at is not None
|
|
586
|
+
|
|
587
|
+
# Do some work with marks
|
|
588
|
+
time.sleep(0.01)
|
|
589
|
+
metric.mark("phase1")
|
|
590
|
+
|
|
591
|
+
time.sleep(0.01)
|
|
592
|
+
metric.mark("phase2")
|
|
593
|
+
|
|
594
|
+
time.sleep(0.01)
|
|
595
|
+
metric.mark("phase3")
|
|
596
|
+
|
|
597
|
+
# Finish
|
|
598
|
+
metric.finish()
|
|
599
|
+
|
|
600
|
+
# Verify results
|
|
601
|
+
assert metric.duration >= 0.03
|
|
602
|
+
assert len(metric.marks) == 3
|
|
603
|
+
assert len(metric.errors) == 0
|
|
604
|
+
|
|
605
|
+
# Check ordering
|
|
606
|
+
assert metric.start_at < metric.marks["phase1"]
|
|
607
|
+
assert metric.marks["phase1"] < metric.marks["phase2"]
|
|
608
|
+
assert metric.marks["phase2"] < metric.marks["phase3"]
|
|
609
|
+
assert metric.marks["phase3"] < metric.finish_at
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def test_metric_context_manager_full_workflow():
|
|
613
|
+
"""Test complete metric workflow with context manager"""
|
|
614
|
+
with Metric() as metric:
|
|
615
|
+
time.sleep(0.01)
|
|
616
|
+
metric.mark("step1")
|
|
617
|
+
|
|
618
|
+
time.sleep(0.01)
|
|
619
|
+
metric.mark("step2")
|
|
620
|
+
|
|
621
|
+
time.sleep(0.01)
|
|
622
|
+
|
|
623
|
+
# Verify
|
|
624
|
+
assert metric.duration >= 0.03
|
|
625
|
+
assert len(metric.marks) == 2
|
|
626
|
+
assert len(metric.errors) == 0
|
|
627
|
+
|
|
628
|
+
# Check data availability
|
|
629
|
+
describe = metric.describe()
|
|
630
|
+
assert "started_at" in describe
|
|
631
|
+
assert "finished_at" in describe
|
|
632
|
+
assert "marks" in describe
|
|
633
|
+
|
|
634
|
+
|
|
635
|
+
def test_metric_reuse_after_context():
|
|
636
|
+
"""Test metric can be reused after context manager"""
|
|
637
|
+
metric = Metric()
|
|
638
|
+
|
|
639
|
+
# First use
|
|
640
|
+
with metric:
|
|
641
|
+
metric.mark("first")
|
|
642
|
+
|
|
643
|
+
first_duration = metric.duration
|
|
644
|
+
|
|
645
|
+
# Second use
|
|
646
|
+
with metric:
|
|
647
|
+
time.sleep(0.02)
|
|
648
|
+
metric.mark("second")
|
|
649
|
+
|
|
650
|
+
second_duration = metric.duration
|
|
651
|
+
|
|
652
|
+
# Second use should have different timing
|
|
653
|
+
assert second_duration != first_duration
|
|
654
|
+
assert "first" not in metric.marks
|
|
655
|
+
assert "second" in metric.marks
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def test_metric_error_handling_workflow():
|
|
659
|
+
"""Test metric properly handles errors in workflow"""
|
|
660
|
+
results = []
|
|
661
|
+
|
|
662
|
+
for i in range(3):
|
|
663
|
+
m = Metric()
|
|
664
|
+
try:
|
|
665
|
+
with m:
|
|
666
|
+
m.mark(f"attempt_{i}")
|
|
667
|
+
if i < 2:
|
|
668
|
+
raise ValueError(f"Error {i}")
|
|
669
|
+
except ValueError:
|
|
670
|
+
pass
|
|
671
|
+
|
|
672
|
+
results.append({"attempt": i, "duration": m.duration, "has_errors": len(m.errors) > 0})
|
|
673
|
+
|
|
674
|
+
# First two should have errors
|
|
675
|
+
assert results[0]["has_errors"]
|
|
676
|
+
assert results[1]["has_errors"]
|
|
677
|
+
assert not results[2]["has_errors"]
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
def test_metric_performance_tracking():
|
|
681
|
+
"""Test metric for performance tracking scenario"""
|
|
682
|
+
metric = Metric()
|
|
683
|
+
metric.start()
|
|
684
|
+
|
|
685
|
+
# Simulate different stages
|
|
686
|
+
stages = ["load", "process", "validate", "save"]
|
|
687
|
+
|
|
688
|
+
for stage in stages:
|
|
689
|
+
time.sleep(0.01)
|
|
690
|
+
metric.mark(stage)
|
|
691
|
+
|
|
692
|
+
metric.finish()
|
|
693
|
+
|
|
694
|
+
# Verify all stages recorded
|
|
695
|
+
assert len(metric.marks) == len(stages)
|
|
696
|
+
for stage in stages:
|
|
697
|
+
assert stage in metric.marks
|
|
698
|
+
|
|
699
|
+
# Verify total duration
|
|
700
|
+
assert metric.duration >= 0.04
|
|
701
|
+
|
|
702
|
+
# Get report
|
|
703
|
+
report = metric.describe()
|
|
704
|
+
assert "marks" in report
|
|
705
|
+
assert len(report["marks"]) == len(stages)
|