@@ -128,6 +128,7 @@ public class PythonSensorTest {
128128 private static final String FILE_COMPLEXITY_RULE_KEY = "FileComplexity" ;
129129 private static final String CUSTOM_REPOSITORY_KEY = "customKey" ;
130130 private static final String CUSTOM_RULE_KEY = "key" ;
131+ private static final String RULE_CRASHING_ON_SCAN_KEY = "key2" ;
131132
132133 private static final Version SONARLINT_DETECTABLE_VERSION = Version .create (6 , 0 );
133134
@@ -141,7 +142,7 @@ public String repositoryKey() {
141142
142143 @ Override
143144 public List <Class > checkClasses () {
144- return Collections . singletonList (MyCustomRule .class );
145+ return List . of (MyCustomRule . class , RuleCrashingOnRegularScan .class );
145146 }
146147 }};
147148 private static Path workDir ;
@@ -177,6 +178,24 @@ public void endOfAnalysis(CacheContext cacheContext) {
177178 }
178179 }
179180
181+ @ Rule (
182+ key = RULE_CRASHING_ON_SCAN_KEY ,
183+ name = "rule_crashing_on_scan" ,
184+ description = "desc" ,
185+ tags = {"bug" })
186+ public static class RuleCrashingOnRegularScan implements PythonCheck {
187+
188+ @ Override
189+ public void scanFile (PythonVisitorContext visitorContext ) {
190+ throw new IllegalStateException ("Should not be executed!" );
191+ }
192+
193+ @ Override
194+ public boolean scanWithoutParsing (PythonInputFileContext inputFile ) {
195+ return true ;
196+ }
197+ }
198+
180199 private final File baseDir = new File ("src/test/resources/org/sonar/plugins/python/sensor" ).getAbsoluteFile ();
181200
182201 private SensorContextTester context ;
@@ -1058,6 +1077,50 @@ public void read_cpd_tokens_cache_disabled() {
10581077 assertThat (tokensLines .get (0 ).getValue ()).isEqualTo ("pass" );
10591078 }
10601079
1080+ @ Test
1081+ public void cpd_tokens_failure_does_not_execute_checks_multiple_times () throws IOException {
1082+ activeRules = new ActiveRulesBuilder ()
1083+ .addRule (new NewActiveRule .Builder ()
1084+ .setRuleKey (RuleKey .of (CUSTOM_REPOSITORY_KEY , RULE_CRASHING_ON_SCAN_KEY ))
1085+ .build ())
1086+ .build ();
1087+
1088+ InputFile inputFile = inputFile ("pass.py" , Type .MAIN , InputFile .Status .SAME );
1089+
1090+ TestReadCache readCache = getValidReadCache ();
1091+ byte [] serializedSymbolTable = toProtobufModuleDescriptor (Collections .emptySet ()).toByteArray ();
1092+ readCache .put (IMPORTS_MAP_CACHE_KEY_PREFIX + inputFile .key (), String .join (";" , Collections .emptyList ()).getBytes (StandardCharsets .UTF_8 ));
1093+ readCache .put (PROJECT_SYMBOL_TABLE_CACHE_KEY_PREFIX + inputFile .key (), serializedSymbolTable );
1094+
1095+ TestWriteCache writeCache = new TestWriteCache ();
1096+ writeCache .bind (readCache );
1097+
1098+ context .setPreviousCache (readCache );
1099+ context .setNextCache (writeCache );
1100+ context .setCacheEnabled (true );
1101+ context .setSettings (
1102+ new MapSettings ()
1103+ .setProperty ("sonar.python.skipUnchanged" , true )
1104+ .setProperty ("sonar.internal.analysis.failFast" , true )
1105+ );
1106+
1107+ sensor ().execute (context );
1108+
1109+ // Verify the written CPD tokens
1110+ List <TokensLine > tokensLines = context .cpdTokens ("moduleKey:pass.py" );
1111+ assertThat (tokensLines )
1112+ .isNotNull ()
1113+ .hasSize (1 );
1114+
1115+ assertThat (tokensLines .get (0 ).getValue ()).isEqualTo ("pass" );
1116+
1117+ // Verify that we carried the tokens over to the next cache
1118+ List <Token > expectedTokens = List .of (new TokenImpl (passToken (inputFile .uri ())));
1119+
1120+ assertThat (writeCache .getData ())
1121+ .containsEntry (Caching .CPD_TOKENS_CACHE_KEY_PREFIX + inputFile .key (), CpdSerializer .toBytes (expectedTokens ));
1122+ }
1123+
10611124 private com .sonar .sslr .api .Token passToken (URI uri ) {
10621125 return com .sonar .sslr .api .Token .builder ()
10631126 .setType (PythonKeyword .PASS )
0 commit comments