@@ -1080,5 +1080,255 @@ TEST_F(ListFamilyTest, ContendExpire) {
10801080 }
10811081}
10821082
1083+ TEST_F (ListFamilyTest, LMPopInvalidSyntax) {
1084+ // Not enough arguments
1085+ auto resp = Run ({" lmpop" , " 1" , " a" });
1086+ EXPECT_THAT (resp, ErrArg (" wrong number of arguments" ));
1087+
1088+ // Zero keys
1089+ resp = Run ({" lmpop" , " 0" , " LEFT" , " COUNT" , " 1" });
1090+ EXPECT_THAT (resp, ErrArg (" syntax error" ));
1091+
1092+ // Number of keys is not uint
1093+ resp = Run ({" lmpop" , " aa" , " a" , " LEFT" });
1094+ EXPECT_THAT (resp, ErrArg (" value is not an integer or out of range" ));
1095+
1096+ // Missing LEFT/RIGHT
1097+ resp = Run ({" lmpop" , " 1" , " a" , " COUNT" , " 1" });
1098+ EXPECT_THAT (resp, ErrArg (" syntax error" ));
1099+
1100+ // Wrong number of keys
1101+ resp = Run ({" lmpop" , " 1" , " a" , " b" , " LEFT" });
1102+ EXPECT_THAT (resp, ErrArg (" syntax error" ));
1103+
1104+ // COUNT without number
1105+ resp = Run ({" lmpop" , " 1" , " a" , " LEFT" , " COUNT" });
1106+ EXPECT_THAT (resp, ErrArg (" syntax error" ));
1107+
1108+ // COUNT is not uint
1109+ resp = Run ({" lmpop" , " 1" , " a" , " LEFT" , " COUNT" , " boo" });
1110+ EXPECT_THAT (resp, ErrArg (" value is not an integer or out of range" ));
1111+
1112+ // Too many arguments
1113+ resp = Run ({" lmpop" , " 1" , " c" , " LEFT" , " COUNT" , " 2" , " foo" });
1114+ EXPECT_THAT (resp, ErrArg (" syntax error" ));
1115+ }
1116+
1117+ TEST_F (ListFamilyTest, LMPop) {
1118+ // All lists are empty
1119+ auto resp = Run ({" lmpop" , " 1" , " e" , " LEFT" });
1120+ EXPECT_THAT (resp, ArgType (RespExpr::NIL));
1121+
1122+ // LEFT operation
1123+ resp = Run ({" lpush" , " a" , " a1" , " a2" });
1124+ EXPECT_THAT (resp, IntArg (2 ));
1125+
1126+ resp = Run ({" lmpop" , " 1" , " a" , " LEFT" });
1127+ EXPECT_THAT (resp, RespArray (ElementsAre (" a" , RespArray (ElementsAre (" a2" )))));
1128+
1129+ // RIGHT operation
1130+ resp = Run ({" lpush" , " b" , " b1" , " b2" });
1131+ EXPECT_THAT (resp, IntArg (2 ));
1132+
1133+ resp = Run ({" lmpop" , " 1" , " b" , " RIGHT" });
1134+ EXPECT_THAT (resp, RespArray (ElementsAre (" b" , RespArray (ElementsAre (" b1" )))));
1135+
1136+ // COUNT > 1
1137+ resp = Run ({" lpush" , " c" , " c1" , " c2" });
1138+ EXPECT_THAT (resp, IntArg (2 ));
1139+
1140+ resp = Run ({" lmpop" , " 1" , " c" , " RIGHT" , " COUNT" , " 2" });
1141+ EXPECT_THAT (resp, RespArray (ElementsAre (" c" , RespArray (ElementsAre (" c1" , " c2" )))));
1142+
1143+ resp = Run ({" llen" , " c" });
1144+ EXPECT_THAT (resp, IntArg (0 ));
1145+
1146+ // COUNT > number of elements in list
1147+ resp = Run ({" lpush" , " d" , " d1" , " d2" });
1148+ EXPECT_THAT (resp, IntArg (2 ));
1149+
1150+ resp = Run ({" lmpop" , " 1" , " d" , " RIGHT" , " COUNT" , " 3" });
1151+ EXPECT_THAT (resp, RespArray (ElementsAre (" d" , RespArray (ElementsAre (" d1" , " d2" )))));
1152+
1153+ resp = Run ({" llen" , " d" });
1154+ EXPECT_THAT (resp, IntArg (0 ));
1155+
1156+ // First non-empty list is not the first list
1157+ resp = Run ({" lpush" , " x" , " x1" });
1158+ EXPECT_THAT (resp, IntArg (1 ));
1159+
1160+ resp = Run ({" lpush" , " y" , " y1" });
1161+ EXPECT_THAT (resp, IntArg (1 ));
1162+
1163+ resp = Run ({" lmpop" , " 3" , " empty" , " x" , " y" , " RIGHT" });
1164+ EXPECT_THAT (resp, RespArray (ElementsAre (" x" , RespArray (ElementsAre (" x1" )))));
1165+
1166+ resp = Run ({" llen" , " x" });
1167+ EXPECT_THAT (resp, IntArg (0 ));
1168+ }
1169+
1170+ TEST_F (ListFamilyTest, LMPopMultipleElements) {
1171+ // Test removing multiple elements from left end
1172+ Run ({" rpush" , " list1" , " a" , " b" , " c" , " d" , " e" });
1173+ auto resp = Run ({" lmpop" , " 1" , " list1" , " LEFT" , " COUNT" , " 3" });
1174+ EXPECT_THAT (resp, RespArray (ElementsAre (" list1" , RespArray (ElementsAre (" a" , " b" , " c" )))));
1175+
1176+ resp = Run ({" lrange" , " list1" , " 0" , " -1" });
1177+ EXPECT_THAT (resp.GetVec (), ElementsAre (" d" , " e" ));
1178+
1179+ // Test removing multiple elements from right end
1180+ Run ({" rpush" , " list2" , " v" , " w" , " x" , " y" , " z" });
1181+ resp = Run ({" lmpop" , " 1" , " list2" , " RIGHT" , " COUNT" , " 2" });
1182+ EXPECT_THAT (resp, RespArray (ElementsAre (" list2" , RespArray (ElementsAre (" z" , " y" )))));
1183+
1184+ resp = Run ({" lrange" , " list2" , " 0" , " -1" });
1185+ EXPECT_THAT (resp.GetVec (), ElementsAre (" v" , " w" , " x" ));
1186+ }
1187+
1188+ TEST_F (ListFamilyTest, LMPopMultipleLists) {
1189+ // Test finding first non-empty list
1190+ Run ({" rpush" , " list1" , " a" , " b" });
1191+ Run ({" rpush" , " list2" , " c" , " d" });
1192+ Run ({" rpush" , " list3" , " e" , " f" });
1193+
1194+ // Pop from first non-empty list
1195+ auto resp = Run ({" lmpop" , " 3" , " list1" , " list2" , " list3" , " LEFT" });
1196+ EXPECT_THAT (resp, RespArray (ElementsAre (" list1" , RespArray (ElementsAre (" a" )))));
1197+
1198+ // Pop from second list after first becomes empty
1199+ Run ({" lmpop" , " 1" , " list1" , " LEFT" }); // Empty list1
1200+ resp = Run ({" lmpop" , " 3" , " list1" , " list2" , " list3" , " RIGHT" , " COUNT" , " 2" });
1201+ EXPECT_THAT (resp, RespArray (ElementsAre (" list2" , RespArray (ElementsAre (" d" , " c" )))));
1202+
1203+ // Verify third list remains untouched
1204+ resp = Run ({" lrange" , " list3" , " 0" , " -1" });
1205+ EXPECT_THAT (resp.GetVec (), ElementsAre (" e" , " f" ));
1206+ }
1207+
1208+ TEST_F (ListFamilyTest, LMPopEdgeCases) {
1209+ // Test with empty list
1210+ Run ({" rpush" , " empty_list" , " a" });
1211+ Run ({" lpop" , " empty_list" });
1212+ auto resp = Run ({" lmpop" , " 1" , " empty_list" , " LEFT" });
1213+ EXPECT_THAT (resp, ArgType (RespExpr::NIL));
1214+
1215+ // Test with non-existent list
1216+ resp = Run ({" lmpop" , " 1" , " nonexistent" , " LEFT" });
1217+ EXPECT_THAT (resp, ArgType (RespExpr::NIL));
1218+
1219+ // Test with wrong type key
1220+ Run ({" set" , " string_key" , " value" });
1221+ resp = Run ({" lmpop" , " 1" , " string_key" , " LEFT" });
1222+ EXPECT_THAT (resp, ErrArg (" WRONGTYPE Operation against a key holding the wrong kind of value" ));
1223+
1224+ // Test without COUNT parameter - should return 1 element by default
1225+ Run ({" rpush" , " list" , " a" , " b" });
1226+ resp = Run ({" lmpop" , " 1" , " list" , " LEFT" });
1227+ EXPECT_THAT (resp,
1228+ RespArray (ElementsAre (
1229+ " list" , RespArray (ElementsAre (" a" ))))); // Should return 1 element by default
1230+
1231+ // Test with COUNT = 0 - should return error
1232+ resp = Run ({" lmpop" , " 1" , " list" , " LEFT" , " COUNT" , " 0" });
1233+ EXPECT_THAT (resp, RespArray (ElementsAre (" list" , RespArray (ElementsAre ()))));
1234+
1235+ // Test with negative COUNT - should return error
1236+ resp = Run ({" lmpop" , " 1" , " list" , " LEFT" , " COUNT" , " -1" });
1237+ EXPECT_THAT (resp, RespArray (ElementsAre (" list" , RespArray (ElementsAre (" b" )))));
1238+ }
1239+
1240+ TEST_F (ListFamilyTest, LMPopDocExample) {
1241+ // Try to pop from non-existing lists
1242+ auto resp = Run ({" LMPOP" , " 2" , " non1" , " non2" , " LEFT" , " COUNT" , " 10" });
1243+ EXPECT_THAT (resp, ArgType (RespExpr::NIL));
1244+
1245+ // Create first list and test basic pop
1246+ resp = Run ({" LPUSH" , " mylist" , " one" , " two" , " three" , " four" , " five" });
1247+ EXPECT_THAT (resp, IntArg (5 ));
1248+
1249+ resp = Run ({" LMPOP" , " 1" , " mylist" , " LEFT" });
1250+ EXPECT_THAT (resp, RespArray (ElementsAre (" mylist" , RespArray (ElementsAre (" five" )))));
1251+
1252+ resp = Run ({" LRANGE" , " mylist" , " 0" , " -1" });
1253+ EXPECT_THAT (resp.GetVec (), ElementsAre (" four" , " three" , " two" , " one" ));
1254+
1255+ // Test RIGHT pop with COUNT
1256+ resp = Run ({" LMPOP" , " 1" , " mylist" , " RIGHT" , " COUNT" , " 10" });
1257+ EXPECT_THAT (resp, RespArray (ElementsAre (" mylist" ,
1258+ RespArray (ElementsAre (" one" , " two" , " three" , " four" )))));
1259+
1260+ // Create two lists and test multi-key pop
1261+ resp = Run ({" LPUSH" , " mylist" , " one" , " two" , " three" , " four" , " five" });
1262+ EXPECT_THAT (resp, IntArg (5 ));
1263+
1264+ resp = Run ({" LPUSH" , " mylist2" , " a" , " b" , " c" , " d" , " e" });
1265+ EXPECT_THAT (resp, IntArg (5 ));
1266+
1267+ resp = Run ({" LMPOP" , " 2" , " mylist" , " mylist2" , " RIGHT" , " COUNT" , " 3" });
1268+ EXPECT_THAT (resp,
1269+ RespArray (ElementsAre (" mylist" , RespArray (ElementsAre (" one" , " two" , " three" )))));
1270+
1271+ resp = Run ({" LRANGE" , " mylist" , " 0" , " -1" });
1272+ EXPECT_THAT (resp.GetVec (), ElementsAre (" five" , " four" ));
1273+
1274+ resp = Run ({" LMPOP" , " 2" , " mylist" , " mylist2" , " RIGHT" , " COUNT" , " 5" });
1275+ EXPECT_THAT (resp, RespArray (ElementsAre (" mylist" , RespArray (ElementsAre (" four" , " five" )))));
1276+
1277+ resp = Run ({" LMPOP" , " 2" , " mylist" , " mylist2" , " RIGHT" , " COUNT" , " 10" });
1278+ EXPECT_THAT (resp,
1279+ RespArray (ElementsAre (" mylist2" , RespArray (ElementsAre (" a" , " b" , " c" , " d" , " e" )))));
1280+
1281+ // Verify both lists are now empty
1282+ resp = Run ({" EXISTS" , " mylist" , " mylist2" });
1283+ EXPECT_THAT (resp, IntArg (0 ));
1284+ }
1285+
1286+ TEST_F (ListFamilyTest, LMPopWrongType) {
1287+ // Setup: create a list and a hash
1288+ Run ({" lpush" , " l1" , " e1" });
1289+ Run ({" hset" , " foo" , " k1" , " v1" });
1290+
1291+ // Test: first key is wrong type
1292+ auto resp = Run ({" lmpop" , " 2" , " foo" , " l1" , " left" });
1293+ EXPECT_THAT (resp, ErrArg (" WRONGTYPE Operation against a key holding the wrong kind of value" ));
1294+
1295+ // Test: second key is wrong type but first doesn't exist
1296+ resp = Run ({" lmpop" , " 2" , " nonexistent" , " foo" , " left" });
1297+ EXPECT_THAT (resp, ErrArg (" WRONGTYPE Operation against a key holding the wrong kind of value" ));
1298+
1299+ // Test: second key is wrong type but first is a valid list
1300+ resp = Run ({" lmpop" , " 2" , " l1" , " foo" , " left" });
1301+ EXPECT_THAT (resp, RespArray (ElementsAre (" l1" , RespArray (ElementsAre (" e1" )))));
1302+ }
1303+
1304+ // Reproduce a flow that trigerred a wrong DCHECK in the transaction flow.
1305+ TEST_F (ListFamilyTest, AwakeMulti) {
1306+ auto f1 = pp_->at (1 )->LaunchFiber (Launch::dispatch, [&] {
1307+ for (unsigned i = 0 ; i < 100 ; ++i) {
1308+ Run (" CONSUMER" , {" blmove" , " src" , " dest" , " LEFT" , " LEFT" , " 0" });
1309+ };
1310+ });
1311+ auto f2 = pp_->at (1 )->LaunchFiber ([&] {
1312+ for (unsigned i = 0 ; i < 100 ; ++i) {
1313+ Run (" PROD" , {" lpush" , " src" , " a" });
1314+ ThisFiber::SleepFor (50us);
1315+ };
1316+ });
1317+
1318+ auto f3 = pp_->at (2 )->LaunchFiber ([&] {
1319+ for (unsigned i = 0 ; i < 100 ; ++i) {
1320+ Run ({" multi" });
1321+ for (unsigned j = 0 ; j < 8 ; ++j) {
1322+ Run ({" get" , StrCat (" key" , j)});
1323+ };
1324+ Run ({" exec" });
1325+ };
1326+ });
1327+
1328+ f1.Join ();
1329+ f2.Join ();
1330+ f3.Join ();
1331+ }
1332+
10831333#pragma GCC diagnostic pop
10841334} // namespace dfly
0 commit comments