@@ -400,6 +400,223 @@ func TestBitmap(t *testing.T) {
400400 require .EqualValues (t , 2 , cmd .Val ())
401401 })
402402
403+ t .Run ("BITPOS BYTE option produces same result as default byte-indexed mode" , func (t * testing.T ) {
404+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \xff \xf0 " , 0 ).Err ())
405+ byteResult := rdb .BitPos (ctx , "mykey" , 1 , 1 )
406+ require .NoError (t , byteResult .Err ())
407+ explicitByte := rdb .BitPosSpan (ctx , "mykey" , 1 , 1 , - 1 , "byte" )
408+ require .NoError (t , explicitByte .Err ())
409+ require .EqualValues (t , byteResult .Val (), explicitByte .Val ())
410+ })
411+
412+ t .Run ("BITPOS BYTE option is case-insensitive" , func (t * testing.T ) {
413+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \xff \xf0 " , 0 ).Err ())
414+ lower , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , - 1 , "byte" ).Int64 ()
415+ require .NoError (t , err )
416+ upper , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , - 1 , "BYTE" ).Int64 ()
417+ require .NoError (t , err )
418+ require .EqualValues (t , lower , upper )
419+ })
420+
421+ t .Run ("BITPOS BIT vs BYTE gives different results for same range" , func (t * testing.T ) {
422+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \xff \xf0 " , 0 ).Err ())
423+ bitResult := rdb .BitPosSpan (ctx , "mykey" , 1 , 0 , 15 , "bit" )
424+ require .NoError (t , bitResult .Err ())
425+ require .EqualValues (t , 8 , bitResult .Val ())
426+ byteResult := rdb .BitPosSpan (ctx , "mykey" , 1 , 0 , 15 , "byte" )
427+ require .NoError (t , byteResult .Err ())
428+ require .EqualValues (t , 8 , byteResult .Val ())
429+ bitResult2 := rdb .BitPosSpan (ctx , "mykey" , 1 , 0 , 7 , "bit" )
430+ require .NoError (t , bitResult2 .Err ())
431+ require .EqualValues (t , - 1 , bitResult2 .Val ())
432+ byteResult2 := rdb .BitPosSpan (ctx , "mykey" , 1 , 0 , 7 , "byte" )
433+ require .NoError (t , byteResult2 .Err ())
434+ require .EqualValues (t , 8 , byteResult2 .Val ())
435+ })
436+
437+ t .Run ("BITPOS rejects invalid option" , func (t * testing.T ) {
438+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
439+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , - 1 , "INVALID" ).Err ()
440+ require .Error (t , err )
441+ })
442+
443+ t .Run ("BITPOS rejects extra arguments after BYTE/BIT" , func (t * testing.T ) {
444+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
445+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , - 1 , "BIT" , "extra" ).Err ()
446+ require .Error (t , err )
447+ })
448+
449+ t .Run ("BITPOS rejects BIT unit without end offset" , func (t * testing.T ) {
450+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x80 " , 0 ).Err ())
451+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , "BIT" ).Err ()
452+ require .ErrorContains (t , err , "not started as an integer" )
453+ })
454+
455+ t .Run ("BITPOS rejects BYTE unit without end offset" , func (t * testing.T ) {
456+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x80 " , 0 ).Err ())
457+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , "BYTE" ).Err ()
458+ require .ErrorContains (t , err , "not started as an integer" )
459+ })
460+
461+ t .Run ("BITPOS rejects non-integer bit argument" , func (t * testing.T ) {
462+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x80 " , 0 ).Err ())
463+ err := rdb .Do (ctx , "BITPOS" , "mykey" , "x" ).Err ()
464+ require .ErrorContains (t , err , "The bit argument must be 1 or 0" )
465+ })
466+
467+ t .Run ("BITPOS rejects non-integer bit argument with BIT unit" , func (t * testing.T ) {
468+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x80 " , 0 ).Err ())
469+ err := rdb .Do (ctx , "BITPOS" , "mykey" , "x" , 0 , 0 , "BIT" ).Err ()
470+ require .ErrorContains (t , err , "The bit argument must be 1 or 0" )
471+ })
472+
473+ t .Run ("BITPOS rejects bit argument of 2" , func (t * testing.T ) {
474+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
475+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 2 ).Err ()
476+ require .ErrorContains (t , err , "The bit argument must be 1 or 0" )
477+ })
478+
479+ t .Run ("BITPOS rejects bit argument of -1" , func (t * testing.T ) {
480+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
481+ err := rdb .Do (ctx , "BITPOS" , "mykey" , - 1 ).Err ()
482+ require .ErrorContains (t , err , "The bit argument must be 1 or 0" )
483+ })
484+
485+ t .Run ("BITPOS rejects non-integer start offset" , func (t * testing.T ) {
486+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
487+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , "abc" ).Err ()
488+ require .ErrorContains (t , err , "not started as an integer" )
489+ })
490+
491+ t .Run ("BITPOS rejects non-integer end offset" , func (t * testing.T ) {
492+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
493+ err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , "abc" ).Err ()
494+ require .ErrorContains (t , err , "not started as an integer" )
495+ })
496+
497+ t .Run ("BITPOS bit=1 with nonexistent key returns -1" , func (t * testing.T ) {
498+ require .NoError (t , rdb .Del (ctx , "nosuchkey" ).Err ())
499+ val , err := rdb .Do (ctx , "BITPOS" , "nosuchkey" , 1 ).Int64 ()
500+ require .NoError (t , err )
501+ require .EqualValues (t , - 1 , val )
502+ })
503+
504+ t .Run ("BITPOS bit=0 with nonexistent key returns 0" , func (t * testing.T ) {
505+ require .NoError (t , rdb .Del (ctx , "nosuchkey" ).Err ())
506+ val , err := rdb .Do (ctx , "BITPOS" , "nosuchkey" , 0 ).Int64 ()
507+ require .NoError (t , err )
508+ require .EqualValues (t , 0 , val )
509+ })
510+
511+ t .Run ("BITPOS BYTE with negative start" , func (t * testing.T ) {
512+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x00 \xff " , 0 ).Err ())
513+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , - 2 , - 1 , "BYTE" ).Int64 ()
514+ require .NoError (t , err )
515+ require .EqualValues (t , 8 , val )
516+ })
517+
518+ t .Run ("BITPOS BIT with negative start and end" , func (t * testing.T ) {
519+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x00 \xff " , 0 ).Err ())
520+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , - 16 , - 9 , "BIT" ).Int64 ()
521+ require .NoError (t , err )
522+ require .EqualValues (t , 8 , val )
523+ })
524+
525+ t .Run ("BITPOS returns -1 when start > end after normalization" , func (t * testing.T ) {
526+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x00 \xff " , 0 ).Err ())
527+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 2 , 1 , "BYTE" ).Int64 ()
528+ require .NoError (t , err )
529+ require .EqualValues (t , - 1 , val )
530+ })
531+
532+ t .Run ("BITPOS BIT returns -1 when start > end after normalization" , func (t * testing.T ) {
533+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x00 \xff " , 0 ).Err ())
534+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 16 , 8 , "BIT" ).Int64 ()
535+ require .NoError (t , err )
536+ require .EqualValues (t , - 1 , val )
537+ })
538+
539+ t .Run ("BITPOS BYTE bit=0 with all-ones string and explicit end returns -1" , func (t * testing.T ) {
540+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \xff \xff " , 0 ).Err ())
541+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , 0 , 2 , "BYTE" ).Int64 ()
542+ require .NoError (t , err )
543+ require .EqualValues (t , - 1 , val )
544+ })
545+
546+ t .Run ("BITPOS BIT bit=0 with all-ones string and explicit end returns -1" , func (t * testing.T ) {
547+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \xff \xff " , 0 ).Err ())
548+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , 0 , 23 , "BIT" ).Int64 ()
549+ require .NoError (t , err )
550+ require .EqualValues (t , - 1 , val )
551+ })
552+
553+ t .Run ("BITPOS BYTE bit=0 without end extends past string (finds trailing zero)" , func (t * testing.T ) {
554+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \xff \xff " , 0 ).Err ())
555+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 ).Int64 ()
556+ require .NoError (t , err )
557+ require .EqualValues (t , 24 , val )
558+ })
559+
560+ t .Run ("BITPOS BYTE with end beyond string length clamps correctly" , func (t * testing.T ) {
561+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \xff \x00 " , 0 ).Err ())
562+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , 100 , "BYTE" ).Int64 ()
563+ require .NoError (t , err )
564+ require .EqualValues (t , 8 , val )
565+ })
566+
567+ t .Run ("BITPOS BIT with end beyond string length clamps correctly" , func (t * testing.T ) {
568+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \xff \x00 " , 0 ).Err ())
569+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 0 , 999 , "BIT" ).Int64 ()
570+ require .NoError (t , err )
571+ require .EqualValues (t , 8 , val )
572+ })
573+
574+ t .Run ("BITPOS BYTE with only start argument" , func (t * testing.T ) {
575+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \x00 \xff " , 0 ).Err ())
576+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 2 ).Int64 ()
577+ require .NoError (t , err )
578+ require .EqualValues (t , 16 , val )
579+ })
580+
581+ t .Run ("BITPOS BYTE with start past string returns -1 for bit=1" , func (t * testing.T ) {
582+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff " , 0 ).Err ())
583+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 5 ).Int64 ()
584+ require .NoError (t , err )
585+ require .EqualValues (t , - 1 , val )
586+ })
587+
588+ t .Run ("BITPOS on wrong type returns WRONGTYPE error" , func (t * testing.T ) {
589+ require .NoError (t , rdb .Del (ctx , "mylist" ).Err ())
590+ require .NoError (t , rdb .LPush (ctx , "mylist" , "a" ).Err ())
591+ err := rdb .Do (ctx , "BITPOS" , "mylist" , 1 ).Err ()
592+ require .ErrorContains (t , err , "WRONGTYPE" )
593+ })
594+
595+ t .Run ("BITPOS BYTE bit=0 finds first zero in middle byte" , func (t * testing.T ) {
596+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x0f \xff " , 0 ).Err ())
597+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , 0 , 2 , "BYTE" ).Int64 ()
598+ require .NoError (t , err )
599+ require .EqualValues (t , 8 , val )
600+ })
601+
602+ t .Run ("BITPOS BIT bit=0 finds first zero within bit range" , func (t * testing.T ) {
603+ require .NoError (t , rdb .Set (ctx , "mykey" , "\xff \x0f \xff " , 0 ).Err ())
604+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 0 , 8 , 15 , "BIT" ).Int64 ()
605+ require .NoError (t , err )
606+ require .EqualValues (t , 8 , val )
607+ })
608+
609+ t .Run ("BITPOS BIT single bit precision check" , func (t * testing.T ) {
610+ require .NoError (t , rdb .Set (ctx , "mykey" , "\x00 \x80 " , 0 ).Err ())
611+ val , err := rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 8 , 8 , "BIT" ).Int64 ()
612+ require .NoError (t , err )
613+ require .EqualValues (t , 8 , val )
614+
615+ val , err = rdb .Do (ctx , "BITPOS" , "mykey" , 1 , 9 , 15 , "BIT" ).Int64 ()
616+ require .NoError (t , err )
617+ require .EqualValues (t , - 1 , val )
618+ })
619+
403620 /* Test cases adapted from redis test cases : https://github.com/redis/redis/blob/unstable/tests/unit/bitops.tcl
404621 */
405622 t .Run ("BITPOS bit=0 with empty key returns 0" , func (t * testing.T ) {
0 commit comments