. "$BST_LIB" # 99-python3-smbus2 -- smbus2 0.4.3, a pure-Python drop-in replacement for # smbus-cffi/smbus-python (meta-openembedded/meta-python # recipes-devtools/python/python3-smbus2_0.4.3.bb -- `inherit pypi setuptools3', # PyPI sdist). # # ============================================================================ # # smbus2 talks to a Linux i2c-dev adapter: it opens /dev/i2c-N, then drives it # with two ioctls -- I2C_SMBUS for the SMBus protocol operations and I2C_RDWR # for combined I2C transactions. It carries no C of its own and binds no # library. It builds the kernel's argument structures ITSELF, in ctypes, and # names the ioctl requests ITSELF, as integers. # # ============================================================================ # THE HEADLINE: `import smbus2' NOW WORKS ON THIS IMAGE. THE ONE PLATFORM GAP # IT WAITED ON HAS CLOSED. # ============================================================================ # # smbus2/__init__.py line 23 re-exports from smbus2/smbus2.py, whose line 26 -- # above every class and every constant in the file -- is `from ctypes import # c_uint32, c_uint8, ...'. This image's python3 used to carry _ctypes # CONFIGURED OUT (hwjs-cc#15), and the import died with # # ModuleNotFoundError: No module named '_ctypes' # # BRINTOS-2269 built _ctypes in, and both `import smbus2' and `import # smbus2.smbus2' now succeed: smbus2 declares ctypes types and loads no library, # so neither hwjs-cc#106 nor the bare-name dlopen gap reaches it. ffi closures # still abort (linux-in-the-browser#180); smbus2 declares none. # # `pkg-import-refused', `submodule-import-refused' and `ctypes-refused' below # were the RED WITNESSES for that gap, in the sense 99-python3.test uses the # term. They went red when it closed and now assert the import succeeding; # the IDs are kept so the history reads across. # # The sections below still read the source rather than drive the class: no # case yet exercises SMBus or measures a ctypes sizeof on this arm. That is # the next thing this file should grow, from an on-target measurement. # # ============================================================================ # WHY THE SUITE WAS WRITTEN AGAINST THE SOURCE (before _ctypes was built). # ============================================================================ # # 99-python3-periphery was the other ctypes-blocked port on this image, and it # reaches three of its seven interfaces by loading them from their file paths -- # led, pwm and serial import nothing but os, errno, time, fcntl, array, termios # and select. NOTHING ANALOGOUS EXISTS HERE. smbus2 is two files; the package # `__init__' does nothing but re-export; and the one implementation module puts # its ctypes import above all six classes, `SMBus' included. `ast-ctypes- # precedes-all-classes' asserts exactly that, from the shipped file, so the # claim is measured rather than argued. # # So this suite reads the source instead, as 99-python3-file-magic does for the # same reason. STUBBING ctypes WOULD BE WORSE THAN MEASURING NOTHING: a fake # c_uint16 answering plausible sizes would keep this suite green while the port # shipped dead, and it would report the STUB's layout rather than this arm's -- # the one thing the structures are worth measuring for. # # ============================================================================ # WHAT IS STILL MEASURED, AND WHY IT IS THE HALF WORTH HAVING. # ============================================================================ # # Everything smbus2 asserts about the kernel interface is a Python literal, so # it is readable without executing a line -- and checkable against the UAPI # headers THIS IMAGE BUILDS AGAINST (linux-brintos-headers, from the # superproject's own kernel). Two families: # # THE SIX IOCTL REQUEST NUMBERS. Checked one by one against i2c-dev.h. # They are PLAIN LITERALS, not `_IOC' arithmetic -- `ast-ioctl-all-literal' # and `ast-ioc-names' assert that there is no _IOC machinery in the module at # all. It is worth stating because it is the opposite of what a reader # expects from an ioctl client, and because it settles the portability # question the ticket was written around: a request built with _IOR/_IOW # encodes sizeof(argument) into its own bits and CAN differ between arms; # these encode nothing, so they are the same integer on wasm32 as anywhere, # and this arm cannot get them wrong. Measured: all six match. # # THE FOUR STRUCTURE DECLARATIONS. `_fields_' is a list of (name, type) # tuples at class scope, so the field ORDER and the declared ctypes type of # each field are readable too, and are checked against the kernel's own struct # definitions field for field. All four agree. # # WHAT THE LAYOUT WOULD BE, AND WHY THIS SUITE DOES NOT ASSERT IT YET. ctypes # computes sizes and offsets at run time from the target ABI, and when this # suite was written there was no ctypes here, so no case below claims a sizeof. For the record, and measured # on the build host by compiling the image's OWN headers with the wasm32 # toolchain (static assertions, BRINTOS-2424): sizeof(void*) is 4, struct # i2c_msg is 12 bytes with buf at offset 8, struct i2c_smbus_ioctl_data is 12 # with data at 8, struct i2c_rdwr_ioctl_data is 8 with nmsgs at 4, and union # i2c_smbus_data is 34. Those are the numbers a working ctypes would have to # reproduce; now that it exists, this file should grow the cases that check it. # # ============================================================================ # THE ADAPTER IS ABSENT, AND THAT IS ASSERTED RATHER THAN ASSUMED. # ============================================================================ # # There is no i2c-dev adapter behind this machine and no /dev/i2c-* node. The # ticket asked for `SMBus(1)' raising FileNotFoundError with ENOENT, the # context-manager form, and the documented per-method errors on an unopened bus. # None of those was reachable when this suite was written -- they need the # class, and the class needed ctypes -- and none is measured yet, so this suite # does not report them, in either direction. What it # does instead is assert the two facts those cases would have rested on: that no # adapter node exists (`no-i2c-dev-node'), and that the path `open()' would have # built is `/dev/i2c-{}' (`devpath-format', read from the source). # # TWO OF THE TICKET'S EXPECTATIONS ARE WRONG, AND THE SOURCE SAYS SO PLAINLY. # There is NO address-range and NO register-range validation in this module: the # only guarded quantity is block length, in four methods, and `block-length- # guarded' names them. An out-of-range address or register is handed to ctypes # conversion and caught -- if at all -- by the C type's own range check, not by # smbus2. And the context-manager form could not have "closed cleanly after # such a failure" either: `SMBus.__init__' calls `open()' itself, so `with # SMBus(1)' raises before `with' ever binds and `__exit__' is never entered. # Both are recorded as what the module DOES, because a reader who assumed the # ticket's version would call the difference a defect of this port. # # ============================================================================ P=python3-smbus2 bst_ok $P/suite-canary require python3 $P/python3-present SITE=/usr/lib/python3.12/site-packages PKG=$SITE/smbus2 DI=$SITE/smbus2-0.4.3.dist-info PKGLIST=/var/lib/dpkg/info/python3-smbus2.list PROBE=$BST_DATA/python3-smbus2-probe.py tmp=${TMPDIR:-/tmp} W=$tmp/bst-smbus2.$$ mkdir -p "$W" || { bst_notok $P/workdir "cannot create $W"; exit 0; } # ------------------------------------------------------------- what shipped -- # IT IS A PACKAGE DIRECTORY, NOT A SINGLE MODULE -- setup.py declares # `packages=['smbus2']', so the implementation lands beside an __init__ that # re-exports it. A release that flattened smbus2 to one module would still # carry every source-level case below, so the shape is asserted directly. check $P/site-packages-dir test -d "$SITE" check $P/package-dir test -d "$PKG" check $P/no-module-file test ! -f "$SITE/smbus2.py" check $P/module-init test -s "$PKG/__init__.py" check $P/module-impl test -s "$PKG/smbus2.py" # THE TYPE STUB AND THE MARKER ARE PACKAGE DATA, and they are the part a # setuptools change could silently drop: a build that lost the declaration would # still ship both .py files and still pass every behavioural case. check $P/stub-smbus2 test -s "$PKG/smbus2.pyi" check $P/py-typed-marker test -f "$PKG/py.typed" check $P/pycache-dir test -d "$PKG/__pycache__" check $P/pycache-init test -s "$PKG/__pycache__/__init__.cpython-312.pyc" check $P/pycache-impl test -s "$PKG/__pycache__/smbus2.cpython-312.pyc" # ----------------------------------------------------------- the .list file -- cat "$PKGLIST" > "$W/pkgf" 2>/dev/null || : > "$W/pkgf" check $P/pkg-list-present test -s "$W/pkgf" # NO SHARED OBJECT ANYWHERE. This package is pure Python and must stay so: a # .so here would be an extension module this image's static interpreter could # not load at all, and it would fail at import rather than at packaging. check_eq $P/pkg-no-so "" \ "$(grep -E '\.so($|\.)' "$W/pkgf" 2>/dev/null)" # ...and nothing outside the shapes the package is made of. _bad="" while IFS= read -r _f; do [ -f "$_f" ] || continue case "$_f" in *.py|*.pyc|*.pyi) continue ;; */py.typed) continue ;; */__pycache__/*) continue ;; */*.dist-info/*) continue ;; esac _bad="$_bad $_f" done < "$W/pkgf" check_eq $P/pkg-only-python "" "$_bad" # NOTHING IN /usr/bin. setup.py declares no `entry_points' and no `scripts', # so this port adds no command -- which is why it has no BRINTOS-780 follow-on. check_eq $P/pkg-no-bin "" \ "$(grep -E '^/usr/bin/' "$W/pkgf" 2>/dev/null)" # ------------------------------------------------------------ the dist-info -- # THE DIRECTORY IS NAMED FOR THE PyPI DISTRIBUTION, and here it agrees with the # import name: `smbus2-0.4.3.dist-info' against `smbus2/'. The recipe's # PYPI_PACKAGE = "smbus2" is what makes the two match. check $P/dist-info-dir test -d "$DI" check $P/dist-info-metadata test -f "$DI/METADATA" check_out $P/dist-info-name "Name: smbus2" cat "$DI/METADATA" check_out $P/dist-info-top-level "smbus2" cat "$DI/top_level.txt" check_out $P/dist-info-purelib "Root-Is-Purelib: true" cat "$DI/WHEEL" check $P/dist-info-license test -s "$DI/LICENSE" # THE VERSION IS REAL, WHICH IS NOT AUTOMATIC. BRINTOS-2209 shipped argcomplete # 3.3.0 whose dist-info said `Version: 0.0.0' -- setuptools_scm re-derived the # version at wheel-build time, found no git in the unpacked sdist and fell back. # smbus2 hardcodes its version in setup.py, so the metadata carries the real # one; asserted here and against the module's own __version__ below, which is # what would catch the two drifting apart. check_out $P/dist-info-version "Version: 0.4.3" cat "$DI/METADATA" # -------------------------------------------------------------- dpkg status -- sed -n '/^Package: python3-smbus2$/,/^$/p' /var/lib/dpkg/status > "$W/st" 2>/dev/null check $P/pkg-known test -s "$W/st" check_out $P/pkg-installed "Status: install ok installed" cat "$W/st" grep '^Depends:' "$W/st" > "$W/dep" 2>/dev/null || : > "$W/dep" check $P/pkg-depends-line test -s "$W/dep" # THE THREE DEPENDENCIES, EACH ASSERTED SEPARATELY. Two come from the recipe's # own RDEPENDS (ctypes, fcntl) and python3-core is implied; all three are # sub-packages of the ported python3 (BRINTOS-1476), so this port waits on no # other package. # # python3-ctypes packages the pure-Python half of ctypes; the C half, _ctypes, # is a builtin of the interpreter itself. for _d in python3-core python3-ctypes python3-fcntl; do check_out $P/pkg-depends-$_d "$_d" cat "$W/dep" done # ---------------------------------------------------- no adapter behind this -- # The premise of every runtime case the ticket asked for, asserted rather than # assumed. i2c-dev exposes each adapter as /dev/i2c-N; there is no adapter and # no node, which is why `SMBus(1)' would raise FileNotFoundError/ENOENT (not yet # measured on target). check_eq $P/no-i2c-dev-node "" "$(ls /dev/i2c-* 2>/dev/null)" # ------------------------------------------------- the import, from a shell -- # This is what a caller on this image actually gets: both imports are silent # now that _ctypes is built (see the header). Both spellings are asserted: the # package, and the implementation module reached directly, so a regression # cannot hide behind the re-export in __init__.py. check_eq $P/pkg-import-refused "" \ "$(python3 -c 'import smbus2' 2>&1 | tail -1)" check_eq $P/submodule-import-refused "" \ "$(python3 -c 'import smbus2.smbus2' 2>&1 | tail -1)" # ------------------------------------------------------------------ the probe -- if [ ! -f "$PROBE" ]; then bst_notok $P/probe-present "$PROBE did not ship" rm -rf "$W" exit 0 fi bst_ok $P/probe-present bst_probe $P/probe-run "$W/probe" 900 python3 "$PROBE" OUT="$W/probe" pv() { grep "^$1=" "$OUT" 2>/dev/null | head -n 1 | cut -d= -f2-; } if [ -s "$OUT" ]; then bst_ok $P/probe-output else bst_notok $P/probe-output "the probe emitted nothing -- python3 did not start, or died before its first line; every CASE below is unscorable" fi check_eq $P/probe-complete "yes" "$(pv probe_done)" check_eq $P/probe-count "107" "$(pv emitted_count)" # The probe found the SAME copy this suite has been asserting against. Without # this case every measurement below is a statement about whatever was first on # sys.path. check_eq $P/probe-site-dir "$SITE" "$(pv site_dir)" check_eq $P/probe-pkg-dir "yes" "$(pv pkg_dir_found)" check_eq $P/probe-module "yes" "$(pv module_found)" check_eq $P/probe-init "yes" "$(pv init_found)" # ------------------------------------------------------- the ctypes boundary -- # The former red witnesses, all four, now asserting the import. The first two # repeat the shell's answer from inside an interpreter that has already # started; the rest are the cause, from the interpreter's own side: ctypes # imports and _ctypes is a builtin of the one static binary. check_eq $P/probe-import-refused "imported" "$(pv pkg_import)" check_eq $P/probe-submodule-refused "imported" "$(pv submodule_import)" check_eq $P/ctypes-refused "imported" "$(pv ctypes_import)" check_eq $P/ctypes-not-builtin "present" "$(pv ctypes_builtin)" # ...and the pure-Python half is installed, which is what the recipe's # python3-ctypes dependency ships. check_eq $P/ctypes-pkg-present "yes" "$(pv ctypes_pkg_present)" # THE OTHER DEPENDENCY IS SOUND. smbus2 needs exactly two things beyond the # core -- ctypes, asserted above, and fcntl.ioctl, which works here. A red in # this block would mean the image is missing more than ctypes and that this # suite is understating the gap. for _m in os sys fcntl; do check_eq $P/stdlib-$_m "ok" "$(pv stdlib_$_m)" done check_eq $P/fcntl-ioctl-usable "ok" "$(pv fcntl_ioctl)" # ------------------------------------------------------- the shipped source -- # IDENTIFIED BY HASH. Every AST case below is a statement about THIS file; the # sha256 is what ties them to it, and what turns a version bump into one obvious # red instead of a scattering of confusing ones. Both files are BYTE-IDENTICAL # to the sdist the recipe's SRC_URI sha256 pins -- checked on the build host by # `cmp' against the unpacked tarball, which is the measurement behind "no patch" # rather than a reading of the recipe. check_eq $P/module-sha256 \ "5c116462ab43db4e9a370ccd0d8525ceaf4d3f7ba5fc8d07057575fefcfb1eb4" \ "$(pv module_sha256)" check_eq $P/module-bytes "21157" "$(pv module_bytes)" check_eq $P/module-lines "658" "$(pv module_lines)" check_eq $P/init-sha256 \ "d87837308b731f612a2f6927d33885683d22b06a3d86b6c4a54d863f113731c3" \ "$(pv init_sha256)" check_eq $P/module-read "ok" "$(pv module_read)" check_eq $P/init-read "ok" "$(pv init_read)" check_eq $P/ast-parse "ok" "$(pv ast_parse)" # ------------------------------------------------------ why nothing is usable -- # THE ONE STRUCTURAL FACT THIS PORT TURNS ON. The ctypes import is at line 26 # and the first class is at line 59, so every class in the module is defined # below the statement that fails. This is the case that says "no importable # fraction" as a measurement rather than as a claim in a comment. check_eq $P/ast-import-ctypes-line "26" "$(pv ast_import_ctypes_line)" check_eq $P/ast-import-fcntl-line "25" "$(pv ast_import_fcntl_line)" check_eq $P/ast-first-class-line "59" "$(pv ast_first_class_line)" check_eq $P/ast-ctypes-precedes-all-classes "yes" \ "$(pv ast_ctypes_precedes_all_classes)" # THE MODULE IMPORTS FOUR THINGS AND NO MORE. os and sys are core, fcntl works # here, ctypes does not -- so the import list IS the port's dependency story, # and a release that added a fifth would change it. check_eq $P/ast-imports "ctypes,fcntl,os,sys" "$(pv ast_imports)" # THE TEN NAMES IT NEEDS FROM ctypes. Every one is a type constructor or a # buffer helper, so not one has a pure-Python fallback that could be substituted # while the C extension is missing. check_eq $P/ast-ctypes-names \ "c_uint32,c_uint8,c_uint16,c_char,POINTER,Structure,Array,Union,create_string_buffer,string_at" \ "$(pv ast_ctypes_names)" check_eq $P/ast-ctypes-name-count "10" "$(pv ast_ctypes_name_count)" # ------------------------------------- the ioctl requests, against the kernel -- # EACH ONE CHECKED AGAINST linux-brintos-headers' include/linux/i2c-dev.h -- the # UAPI headers THIS IMAGE BUILDS AGAINST, from the superproject's own kernel. # The expected values here are that header's, not upstream smbus2's: the point # of the case is that the two agree. Decimal, because that is what the probe # emits; the hex the header writes is beside each. check_eq $P/ioctl-i2c-slave "1795" "$(pv const_I2C_SLAVE)" # 0x0703 check_eq $P/ioctl-i2c-slave-force "1798" "$(pv const_I2C_SLAVE_FORCE)" # 0x0706 check_eq $P/ioctl-i2c-funcs "1797" "$(pv const_I2C_FUNCS)" # 0x0705 check_eq $P/ioctl-i2c-rdwr "1799" "$(pv const_I2C_RDWR)" # 0x0707 check_eq $P/ioctl-i2c-smbus "1824" "$(pv const_I2C_SMBUS)" # 0x0720 check_eq $P/ioctl-i2c-pec "1800" "$(pv const_I2C_PEC)" # 0x0708 # NO _IOC ARITHMETIC ANYWHERE. This is the portability answer for this arm: a # request number built with _IOR/_IOW encodes sizeof(argument) in its own bits # and can differ where a pointer or a long differs; these are bare literals, so # they encode nothing and cannot be got wrong by wasm32. Asserted two ways -- # no non-literal among the request names, and no _IOC name in the module at all. check_eq $P/ast-ioctl-all-literal "yes" "$(pv ast_ioctl_all_literal)" check_eq $P/ast-ioctl-nonliteral "" "$(pv ast_ioctl_nonliteral)" check_eq $P/ast-no-ioc-machinery "" "$(pv ast_ioc_names)" # The protocol constants from uapi/linux/i2c.h, which select what an I2C_SMBUS # transfer does. Same source, same standard, and they are what a caller's # `size' argument is drawn from. check_eq $P/const-smbus-write "0" "$(pv const_I2C_SMBUS_WRITE)" check_eq $P/const-smbus-read "1" "$(pv const_I2C_SMBUS_READ)" check_eq $P/const-smbus-quick "0" "$(pv const_I2C_SMBUS_QUICK)" check_eq $P/const-smbus-byte "1" "$(pv const_I2C_SMBUS_BYTE)" check_eq $P/const-smbus-byte-data "2" "$(pv const_I2C_SMBUS_BYTE_DATA)" check_eq $P/const-smbus-word-data "3" "$(pv const_I2C_SMBUS_WORD_DATA)" check_eq $P/const-smbus-proc-call "4" "$(pv const_I2C_SMBUS_PROC_CALL)" check_eq $P/const-smbus-block-data "5" "$(pv const_I2C_SMBUS_BLOCK_DATA)" check_eq $P/const-smbus-block-proc "7" "$(pv const_I2C_SMBUS_BLOCK_PROC_CALL)" check_eq $P/const-smbus-i2c-block "8" "$(pv const_I2C_SMBUS_I2C_BLOCK_DATA)" # I2C_SMBUS_BLOCK_MAX is the kernel's own 32, and I2C_M_RD the read flag from # uapi/linux/i2c.h. Both match the headers this image builds against. check_eq $P/const-block-max "32" "$(pv const_I2C_SMBUS_BLOCK_MAX)" check_eq $P/const-m-rd "1" "$(pv const_I2C_M_RD)" # ------------------------------- the structure declarations, against the kernel -- # `_fields_' is a plain list of (name, type) tuples at class scope, so the field # ORDER and each field's declared ctypes type are readable without ctypes. Each # is checked against the kernel struct of the same name, field for field. # # struct i2c_msg { __u16 addr; __u16 flags; __u16 len; __u8 *buf; } check_eq $P/fields-i2c-msg \ "addr:c_uint16,flags:c_uint16,len:c_uint16,buf:POINTER(c_char)" \ "$(pv fields_i2c_msg)" # struct i2c_smbus_ioctl_data { __u8 read_write; __u8 command; __u32 size; # union i2c_smbus_data *data; } check_eq $P/fields-i2c-smbus-ioctl-data \ "read_write:c_uint8,command:c_uint8,size:c_uint32,data:union_pointer_type" \ "$(pv fields_i2c_smbus_ioctl_data)" # struct i2c_rdwr_ioctl_data { struct i2c_msg *msgs; __u32 nmsgs; } check_eq $P/fields-i2c-rdwr-ioctl-data \ "msgs:POINTER(i2c_msg),nmsgs:c_uint32" \ "$(pv fields_i2c_rdwr_ioctl_data)" # union i2c_smbus_data { __u8 byte; __u16 word; __u8 block[BLOCK_MAX+2]; } check_eq $P/fields-union-i2c-smbus-data \ "byte:c_uint8,word:c_uint16,block:i2c_smbus_data" \ "$(pv fields_union_i2c_smbus_data)" # THE BASE CLASS IS PART OF THE LAYOUT. The same `_fields_' list means one # thing under Structure and another under Union, so a release that changed a # base would relocate every field while leaving all four cases above green. check_eq $P/base-i2c-msg "Structure" "$(pv base_i2c_msg)" check_eq $P/base-i2c-smbus-ioctl-data "Structure" "$(pv base_i2c_smbus_ioctl_data)" check_eq $P/base-i2c-rdwr-ioctl-data "Structure" "$(pv base_i2c_rdwr_ioctl_data)" check_eq $P/base-union-i2c-smbus-data "Union" "$(pv base_union_i2c_smbus_data)" check_eq $P/base-i2c-smbus-data "Array" "$(pv base_i2c_smbus_data)" # THE BLOCK BUFFER IS SIZED FROM THE CONSTANT, NOT RESTATED. The kernel writes # `block[I2C_SMBUS_BLOCK_MAX + 2]' -- 32 payload bytes, one length byte and one # more for user-space compatibility -- and smbus2 writes the same expression. # The probe reports the expression and the computed value separately, so a # release that hardcoded 34 would be visible as such rather than silently equal. check_eq $P/array-length-expr "I2C_SMBUS_BLOCK_MAX+2" "$(pv array_length_expr)" check_eq $P/array-length-value "34" "$(pv array_length_value)" check_eq $P/array-type "c_uint8" "$(pv array_type)" check_eq $P/ast-classes \ "I2cFunc,i2c_smbus_data,union_i2c_smbus_data,i2c_smbus_ioctl_data,i2c_msg,i2c_rdwr_ioctl_data,SMBus" \ "$(pv ast_classes)" # ----------------------------------------------------------- the API surface -- # THE DOCUMENTED METHODS, AND THEIR SIGNATURES, from the source rather than from # dir() -- which is unavailable, the class never having been created. `force=' # marks a parameter carrying a default. This is the drop-in contract smbus2 # exists to provide, so a release that renamed a parameter would break callers # silently and is caught here. check_eq $P/smbus-method-count "23" "$(pv smbus_method_count)" check_eq $P/sig-init "self,bus=,force=" "$(pv sig___init__)" check_eq $P/sig-open "self,bus" "$(pv sig_open)" check_eq $P/sig-close "self" "$(pv sig_close)" check_eq $P/sig-enter "self" "$(pv sig___enter__)" check_eq $P/sig-exit "self,exc_type,exc_val,exc_tb" "$(pv sig___exit__)" check_eq $P/sig-write-quick "self,i2c_addr,force=" "$(pv sig_write_quick)" check_eq $P/sig-read-byte "self,i2c_addr,force=" "$(pv sig_read_byte)" check_eq $P/sig-write-byte "self,i2c_addr,value,force=" "$(pv sig_write_byte)" check_eq $P/sig-read-byte-data "self,i2c_addr,register,force=" "$(pv sig_read_byte_data)" check_eq $P/sig-write-byte-data "self,i2c_addr,register,value,force=" "$(pv sig_write_byte_data)" check_eq $P/sig-read-word-data "self,i2c_addr,register,force=" "$(pv sig_read_word_data)" check_eq $P/sig-write-word-data "self,i2c_addr,register,value,force=" "$(pv sig_write_word_data)" check_eq $P/sig-process-call "self,i2c_addr,register,value,force=" "$(pv sig_process_call)" check_eq $P/sig-read-block-data "self,i2c_addr,register,force=" "$(pv sig_read_block_data)" check_eq $P/sig-write-block-data "self,i2c_addr,register,data,force=" "$(pv sig_write_block_data)" check_eq $P/sig-block-process-call "self,i2c_addr,register,data,force=" "$(pv sig_block_process_call)" check_eq $P/sig-read-i2c-block "self,i2c_addr,register,length,force=" "$(pv sig_read_i2c_block_data)" check_eq $P/sig-write-i2c-block "self,i2c_addr,register,data,force=" "$(pv sig_write_i2c_block_data)" check_eq $P/sig-enable-pec "self,enable=" "$(pv sig_enable_pec)" # i2c_rdwr takes ITS MESSAGES AS VARARGS, not as a list -- the one signature in # the class a caller is likely to get wrong, and the reason it is asserted apart # from the block above. check_eq $P/sig-i2c-rdwr "self,*i2c_msgs" "$(pv sig_i2c_rdwr)" # i2c_msg's two factories, and the dunders that make a message iterable, sized, # printable and convertible to bytes. `read' takes a length and `write' takes a # buffer; both are staticmethods returning a new message. check_eq $P/sig-i2c-msg-read "address,length" "$(pv sig_i2c_msg_read)" check_eq $P/sig-i2c-msg-write "address,buf" "$(pv sig_i2c_msg_write)" check_eq $P/i2c-msg-dunders \ "__bytes__,__iter__,__len__,__repr__,__str__" "$(pv i2c_msg_dunders)" # --------------------------------------------- what is validated, and what is not -- # THE TICKET EXPECTED ADDRESS-RANGE AND REGISTER-RANGE VALIDATION. IT IS NOT # THERE. The module raises in six places only: four ValueError for block length # (named below), one TypeError for a bus argument that is neither int nor str, # and one IOError when PEC is enabled on an adapter that does not offer it. An # out-of-range address or register reaches ctypes conversion untouched. Pinned # as counts so a release that ADDED validation is a red here rather than a # silent change of contract. check_eq $P/raise-kinds "IOError,TypeError,ValueError" "$(pv raise_kinds)" check_eq $P/raise-valueerror "4" "$(pv raise_ValueError)" check_eq $P/raise-typeerror "1" "$(pv raise_TypeError)" check_eq $P/raise-ioerror "1" "$(pv raise_IOError)" # THE FOUR BLOCK-LENGTH GUARDS, NAMED. Each compares a length against # I2C_SMBUS_BLOCK_MAX and raises before building any ctypes object -- which is # the one part of the ticket's "validation before any ioctl" that is real. check_eq $P/block-length-guarded \ "block_process_call,read_i2c_block_data,write_block_data,write_i2c_block_data" \ "$(pv block_length_guarded)" # THE PATH `open()' WOULD BUILD from an integer bus number. It is the literal # that connects this module to i2c-dev, and with no adapter present it is what # would have produced the ticket's FileNotFoundError/ENOENT. check_eq $P/devpath-format "/dev/i2c-{}" "$(pv devpath_format)" # ---------------------------------------------------------------- the version -- # THE MODULE'S OWN __version__, read from __init__.py, and its __all__. The # dist-info METADATA was asserted above; these are the other side of that pair, # and a release where the two disagreed would show up here as one red. check_eq $P/init-parse "ok" "$(pv init_parse)" check_eq $P/init-version "0.4.3" "$(pv init_version)" check_eq $P/init-all "SMBus,i2c_msg,I2cFunc" "$(pv init_all)" # ------------------------------------------------------------------- I2cFunc -- # The functionality mask, from uapi/linux/i2c.h. I2cFunc is the one class that # is NOT a ctypes type -- it is an IntFlag. Its members are literals, so what # this port would report for an adapter's capabilities is readable here. check_eq $P/base-i2cfunc "IntFlag" "$(pv base_I2cFunc)" check_eq $P/i2cfunc-member-count "26" "$(pv i2cfunc_member_count)" check_eq $P/i2cfunc-i2c "1" "$(pv i2cfunc_I2C)" check_eq $P/i2cfunc-smbus-pec "8" "$(pv i2cfunc_SMBUS_PEC)" check_eq $P/i2cfunc-smbus-quick "65536" "$(pv i2cfunc_SMBUS_QUICK)" check_eq $P/i2cfunc-read-byte "131072" "$(pv i2cfunc_SMBUS_READ_BYTE)" check_eq $P/i2cfunc-write-byte "262144" "$(pv i2cfunc_SMBUS_WRITE_BYTE)" check_eq $P/i2cfunc-emul "251592712" "$(pv i2cfunc_SMBUS_EMUL)" # SMBUS_BYTE really is READ_BYTE|WRITE_BYTE, computed by the probe rather than # restated here -- the composite the kernel defines, and the shape every other # SMBUS_* pair in the mask follows. check_eq $P/i2cfunc-byte "393216" "$(pv i2cfunc_SMBUS_BYTE)" check_eq $P/i2cfunc-byte-composite "yes" "$(pv i2cfunc_byte_is_composite)" rm -rf "$W"