|
1 | 1 | use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
|
2 |
| -use postgres::Client; |
| 2 | +use postgres::{fallible_iterator::FallibleIterator, Client}; |
3 | 3 | use postgres_openssl::MakeTlsConnector;
|
4 | 4 | use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
| 5 | +use state_map::StateMap; |
5 | 6 | use std::{borrow::Cow, collections::BTreeMap, fmt};
|
| 7 | +use string_cache::DefaultAtom as Atom; |
6 | 8 |
|
7 | 9 | use synapse_compress_state::StateGroupEntry;
|
8 | 10 |
|
@@ -105,3 +107,230 @@ impl<'a> fmt::Display for PGEscape<'a> {
|
105 | 107 | write!(f, "{}{}{}", delim, self.0, delim)
|
106 | 108 | }
|
107 | 109 | }
|
| 110 | + |
| 111 | +/// Checks whether the state at each state group is the same as what the map thinks it should be |
| 112 | +/// |
| 113 | +/// i.e. when synapse tries to work out the state for a given state group by looking at |
| 114 | +/// the database. Will the state it gets be the same as what the map thinks it should be |
| 115 | +pub fn database_collapsed_states_match_map( |
| 116 | + state_group_map: &BTreeMap<i64, StateGroupEntry>, |
| 117 | +) -> bool { |
| 118 | + for sg in state_group_map.keys() { |
| 119 | + let map_state = collapse_state_with_map(state_group_map, *sg); |
| 120 | + let database_state = collapse_state_with_database(*sg); |
| 121 | + if map_state != database_state { |
| 122 | + println!("database state {} doesn't match", sg); |
| 123 | + println!("expected {:?}", map_state); |
| 124 | + println!("but found {:?}", database_state); |
| 125 | + return false; |
| 126 | + } |
| 127 | + } |
| 128 | + true |
| 129 | +} |
| 130 | + |
| 131 | +/// Gets the full state for a given group from the map (of deltas) |
| 132 | +fn collapse_state_with_map( |
| 133 | + map: &BTreeMap<i64, StateGroupEntry>, |
| 134 | + state_group: i64, |
| 135 | +) -> StateMap<Atom> { |
| 136 | + let mut entry = &map[&state_group]; |
| 137 | + let mut state_map = StateMap::new(); |
| 138 | + |
| 139 | + let mut stack = vec![state_group]; |
| 140 | + |
| 141 | + while let Some(prev_state_group) = entry.prev_state_group { |
| 142 | + stack.push(prev_state_group); |
| 143 | + if !map.contains_key(&prev_state_group) { |
| 144 | + panic!("Missing {}", prev_state_group); |
| 145 | + } |
| 146 | + entry = &map[&prev_state_group]; |
| 147 | + } |
| 148 | + |
| 149 | + for sg in stack.iter().rev() { |
| 150 | + state_map.extend( |
| 151 | + map[sg] |
| 152 | + .state_map |
| 153 | + .iter() |
| 154 | + .map(|((t, s), e)| ((t, s), e.clone())), |
| 155 | + ); |
| 156 | + } |
| 157 | + |
| 158 | + state_map |
| 159 | +} |
| 160 | + |
| 161 | +fn collapse_state_with_database(state_group: i64) -> StateMap<Atom> { |
| 162 | + // connect to the database |
| 163 | + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); |
| 164 | + builder.set_verify(SslVerifyMode::NONE); |
| 165 | + let connector = MakeTlsConnector::new(builder.build()); |
| 166 | + |
| 167 | + let mut client = Client::connect(DB_URL, connector).unwrap(); |
| 168 | + |
| 169 | + // Gets the delta for a specific state group |
| 170 | + let query_deltas = r#" |
| 171 | + SELECT m.id, type, state_key, s.event_id |
| 172 | + FROM state_groups AS m |
| 173 | + LEFT JOIN state_groups_state AS s ON (m.id = s.state_group) |
| 174 | + WHERE m.id = $1 |
| 175 | + "#; |
| 176 | + |
| 177 | + // If there is no delta for that specific state group, then we still want to find |
| 178 | + // the predecessor (so have split this into a different query) |
| 179 | + let query_pred = r#" |
| 180 | + SELECT prev_state_group |
| 181 | + FROM state_group_edges |
| 182 | + WHERE state_group = $1 |
| 183 | + "#; |
| 184 | + |
| 185 | + let mut state_map: StateMap<Atom> = StateMap::new(); |
| 186 | + |
| 187 | + let mut next_group = Some(state_group); |
| 188 | + |
| 189 | + while let Some(sg) = next_group { |
| 190 | + // get predecessor from state_group_edges |
| 191 | + let mut pred = client.query_raw(query_pred, &[sg]).unwrap(); |
| 192 | + |
| 193 | + // set next_group to predecessor |
| 194 | + next_group = match pred.next().unwrap() { |
| 195 | + Some(p) => p.get(0), |
| 196 | + None => None, |
| 197 | + }; |
| 198 | + |
| 199 | + // if there was a predecessor then assert that it is unique |
| 200 | + if next_group.is_some() { |
| 201 | + assert!(pred.next().unwrap().is_none()); |
| 202 | + } |
| 203 | + drop(pred); |
| 204 | + |
| 205 | + let mut rows = client.query_raw(query_deltas, &[sg]).unwrap(); |
| 206 | + |
| 207 | + while let Some(row) = rows.next().unwrap() { |
| 208 | + // Copy the single delta from the predecessor stored in this row |
| 209 | + if let Some(etype) = row.get::<_, Option<String>>(1) { |
| 210 | + let key = &row.get::<_, String>(2); |
| 211 | + |
| 212 | + // only insert if not overriding existing entry |
| 213 | + // this is because the newer delta is found FIRST |
| 214 | + if state_map.get(&etype, key).is_none() { |
| 215 | + state_map.insert(&etype, key, row.get::<_, String>(3).into()); |
| 216 | + } |
| 217 | + } |
| 218 | + } |
| 219 | + } |
| 220 | + |
| 221 | + state_map |
| 222 | +} |
| 223 | + |
| 224 | +/// Check whether predecessors and deltas stored in the database are the same as in the map |
| 225 | +pub fn database_structure_matches_map(state_group_map: &BTreeMap<i64, StateGroupEntry>) -> bool { |
| 226 | + // connect to the database |
| 227 | + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); |
| 228 | + builder.set_verify(SslVerifyMode::NONE); |
| 229 | + let connector = MakeTlsConnector::new(builder.build()); |
| 230 | + |
| 231 | + let mut client = Client::connect(DB_URL, connector).unwrap(); |
| 232 | + |
| 233 | + // Gets the delta for a specific state group |
| 234 | + let query_deltas = r#" |
| 235 | + SELECT m.id, type, state_key, s.event_id |
| 236 | + FROM state_groups AS m |
| 237 | + LEFT JOIN state_groups_state AS s ON (m.id = s.state_group) |
| 238 | + WHERE m.id = $1 |
| 239 | + "#; |
| 240 | + |
| 241 | + // If there is no delta for that specific state group, then we still want to find |
| 242 | + // the predecessor (so have split this into a different query) |
| 243 | + let query_pred = r#" |
| 244 | + SELECT prev_state_group |
| 245 | + FROM state_group_edges |
| 246 | + WHERE state_group = $1 |
| 247 | + "#; |
| 248 | + |
| 249 | + for (sg, entry) in state_group_map { |
| 250 | + // get predecessor from state_group_edges |
| 251 | + let mut pred_iter = client.query_raw(query_pred, &[sg]).unwrap(); |
| 252 | + |
| 253 | + // read out the predecessor value from the database |
| 254 | + let database_pred = match pred_iter.next().unwrap() { |
| 255 | + Some(p) => p.get(0), |
| 256 | + None => None, |
| 257 | + }; |
| 258 | + |
| 259 | + // if there was a predecessor then assert that it is unique |
| 260 | + if database_pred.is_some() { |
| 261 | + assert!(pred_iter.next().unwrap().is_none()); |
| 262 | + } |
| 263 | + |
| 264 | + // check if it matches map |
| 265 | + if database_pred != entry.prev_state_group { |
| 266 | + println!( |
| 267 | + "ERROR: predecessor for {} was {:?} (expected {:?})", |
| 268 | + sg, database_pred, entry.prev_state_group |
| 269 | + ); |
| 270 | + return false; |
| 271 | + } |
| 272 | + // needed so that can create another query |
| 273 | + drop(pred_iter); |
| 274 | + |
| 275 | + // Now check that deltas are the same |
| 276 | + let mut state_map: StateMap<Atom> = StateMap::new(); |
| 277 | + |
| 278 | + // Get delta from state_groups_state |
| 279 | + let mut rows = client.query_raw(query_deltas, &[sg]).unwrap(); |
| 280 | + |
| 281 | + while let Some(row) = rows.next().unwrap() { |
| 282 | + // Copy the single delta from the predecessor stored in this row |
| 283 | + if let Some(etype) = row.get::<_, Option<String>>(1) { |
| 284 | + state_map.insert( |
| 285 | + &etype, |
| 286 | + &row.get::<_, String>(2), |
| 287 | + row.get::<_, String>(3).into(), |
| 288 | + ); |
| 289 | + } |
| 290 | + } |
| 291 | + |
| 292 | + // Check that the delta matches the map |
| 293 | + if state_map != entry.state_map { |
| 294 | + println!("ERROR: delta for {} didn't match", sg); |
| 295 | + println!("Expected: {:?}", entry.state_map); |
| 296 | + println!("Actual: {:?}", state_map); |
| 297 | + return false; |
| 298 | + } |
| 299 | + } |
| 300 | + true |
| 301 | +} |
| 302 | + |
| 303 | +#[test] |
| 304 | +fn functions_are_self_consistent() { |
| 305 | + let mut initial: BTreeMap<i64, StateGroupEntry> = BTreeMap::new(); |
| 306 | + let mut prev = None; |
| 307 | + |
| 308 | + // This starts with the following structure |
| 309 | + // |
| 310 | + // 0-1-2-3-4-5-6-7-8-9-10-11-12-13 |
| 311 | + // |
| 312 | + // Each group i has state: |
| 313 | + // ('node','is', i) |
| 314 | + // ('group', j, 'seen') - for all j less than i |
| 315 | + for i in 0i64..=13i64 { |
| 316 | + let mut entry = StateGroupEntry { |
| 317 | + in_range: true, |
| 318 | + prev_state_group: prev, |
| 319 | + state_map: StateMap::new(), |
| 320 | + }; |
| 321 | + entry |
| 322 | + .state_map |
| 323 | + .insert("group", &i.to_string(), "seen".into()); |
| 324 | + entry.state_map.insert("node", "is", i.to_string().into()); |
| 325 | + |
| 326 | + initial.insert(i, entry); |
| 327 | + |
| 328 | + prev = Some(i) |
| 329 | + } |
| 330 | + |
| 331 | + empty_database(); |
| 332 | + add_contents_to_database("room1", &initial); |
| 333 | + |
| 334 | + assert!(database_collapsed_states_match_map(&initial)); |
| 335 | + assert!(database_structure_matches_map(&initial)); |
| 336 | +} |
0 commit comments